From 05d48ed03ee889333b1cf9f24fb40625bb5ffc77 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sylwester=20Zieli=C5=84ski?= Date: Tue, 1 Feb 2022 14:47:58 +0100 Subject: [PATCH] Add Link Loss support for prx --- .../android/service/BleManagerStatus.kt | 13 ++++++++ .../android/service/BleProfileService.kt | 17 +++++----- .../android/cgms/repository/CGMService.kt | 7 ++++- .../android/csc/repository/CSCService.kt | 8 +++-- .../android/hrs/service/HRSService.kt | 9 +++++- .../android/hts/repository/HTSService.kt | 9 +++++- .../no/nordicsemi/android/prx/data/PRXData.kt | 3 +- .../android/prx/data/PRXRepository.kt | 16 +++++++--- .../android/prx/repository/AlarmHandler.kt | 31 +++++++++++++------ .../android/prx/repository/PRXManager.kt | 23 ++++++++++---- .../android/prx/repository/PRXService.kt | 24 ++++++++++++-- .../android/rscs/repository/RSCSService.kt | 9 +++++- .../android/uart/repository/UARTService.kt | 7 ++++- 13 files changed, 139 insertions(+), 37 deletions(-) diff --git a/lib_service/src/main/java/no/nordicsemi/android/service/BleManagerStatus.kt b/lib_service/src/main/java/no/nordicsemi/android/service/BleManagerStatus.kt index 1ce97078..a546f678 100644 --- a/lib_service/src/main/java/no/nordicsemi/android/service/BleManagerStatus.kt +++ b/lib_service/src/main/java/no/nordicsemi/android/service/BleManagerStatus.kt @@ -3,3 +3,16 @@ package no.nordicsemi.android.service enum class BleManagerStatus { CONNECTING, OK, DISCONNECTED } + +enum class BleServiceStatus { + CONNECTING, OK, DISCONNECTED, LINK_LOSS; + + fun mapToSimpleManagerStatus(): BleManagerStatus { + return when (this) { + CONNECTING -> BleManagerStatus.CONNECTING + OK -> BleManagerStatus.OK + DISCONNECTED -> BleManagerStatus.DISCONNECTED + LINK_LOSS -> BleManagerStatus.DISCONNECTED + } + } +} diff --git a/lib_service/src/main/java/no/nordicsemi/android/service/BleProfileService.kt b/lib_service/src/main/java/no/nordicsemi/android/service/BleProfileService.kt index 3ee82e82..d03d584f 100644 --- a/lib_service/src/main/java/no/nordicsemi/android/service/BleProfileService.kt +++ b/lib_service/src/main/java/no/nordicsemi/android/service/BleProfileService.kt @@ -33,6 +33,7 @@ import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow import no.nordicsemi.android.ble.BleManager +import no.nordicsemi.android.ble.observer.ConnectionObserver import no.nordicsemi.android.log.ILogSession import no.nordicsemi.android.log.Logger import no.nordicsemi.ui.scanner.DiscoveredBluetoothDevice @@ -44,7 +45,7 @@ abstract class BleProfileService : Service() { protected abstract val manager: BleManager - private val _status = MutableStateFlow(BleManagerStatus.CONNECTING) + private val _status = MutableStateFlow(BleServiceStatus.CONNECTING) val status = _status.asStateFlow() /** @@ -71,20 +72,22 @@ abstract class BleProfileService : Service() { manager.setConnectionObserver(object : ConnectionObserverAdapter() { override fun onDeviceConnected(device: BluetoothDevice) { super.onDeviceConnected(device) - _status.value = BleManagerStatus.OK + _status.value = BleServiceStatus.OK } override fun onDeviceFailedToConnect(device: BluetoothDevice, reason: Int) { super.onDeviceFailedToConnect(device, reason) - _status.value = BleManagerStatus.DISCONNECTED + _status.value = BleServiceStatus.DISCONNECTED stopSelf() - scope.close() } override fun onDeviceDisconnected(device: BluetoothDevice, reason: Int) { super.onDeviceDisconnected(device, reason) - _status.value = BleManagerStatus.DISCONNECTED - scope.close() + if (reason == ConnectionObserver.REASON_LINK_LOSS) { + _status.value = BleServiceStatus.LINK_LOSS + } else { + _status.value = BleServiceStatus.DISCONNECTED + } } }) } @@ -98,7 +101,7 @@ abstract class BleProfileService : Service() { * * @return true to use autoConnect feature, false (default) otherwise. */ - private fun shouldAutoConnect(): Boolean { + protected open fun shouldAutoConnect(): Boolean { return false } diff --git a/profile_cgms/src/main/java/no/nordicsemi/android/cgms/repository/CGMService.kt b/profile_cgms/src/main/java/no/nordicsemi/android/cgms/repository/CGMService.kt index 5ce8f8da..32085512 100644 --- a/profile_cgms/src/main/java/no/nordicsemi/android/cgms/repository/CGMService.kt +++ b/profile_cgms/src/main/java/no/nordicsemi/android/cgms/repository/CGMService.kt @@ -5,6 +5,7 @@ import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach import no.nordicsemi.android.cgms.data.CGMRepository import no.nordicsemi.android.cgms.data.CGMServiceCommand +import no.nordicsemi.android.service.BleManagerStatus import no.nordicsemi.android.service.ForegroundBleService import no.nordicsemi.android.utils.exhaustive import javax.inject.Inject @@ -21,7 +22,11 @@ internal class CGMService : ForegroundBleService() { super.onCreate() status.onEach { - repository.setNewStatus(it) + val status = it.mapToSimpleManagerStatus() + repository.setNewStatus(status) + if (status == BleManagerStatus.DISCONNECTED) { + scope.close() + } }.launchIn(scope) repository.command.onEach { diff --git a/profile_csc/src/main/java/no/nordicsemi/android/csc/repository/CSCService.kt b/profile_csc/src/main/java/no/nordicsemi/android/csc/repository/CSCService.kt index 3349c54e..a6a89a4f 100644 --- a/profile_csc/src/main/java/no/nordicsemi/android/csc/repository/CSCService.kt +++ b/profile_csc/src/main/java/no/nordicsemi/android/csc/repository/CSCService.kt @@ -1,12 +1,12 @@ package no.nordicsemi.android.csc.repository -import android.util.Log import dagger.hilt.android.AndroidEntryPoint import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach import no.nordicsemi.android.csc.data.CSCRepository import no.nordicsemi.android.csc.data.DisconnectCommand import no.nordicsemi.android.csc.data.SetWheelSizeCommand +import no.nordicsemi.android.service.BleManagerStatus import no.nordicsemi.android.service.ForegroundBleService import no.nordicsemi.android.utils.exhaustive import javax.inject.Inject @@ -23,7 +23,11 @@ internal class CSCService : ForegroundBleService() { super.onCreate() status.onEach { - repository.setNewStatus(it) + val status = it.mapToSimpleManagerStatus() + repository.setNewStatus(status) + if (status == BleManagerStatus.DISCONNECTED) { + scope.close() + } }.launchIn(scope) repository.command.onEach { diff --git a/profile_hrs/src/main/java/no/nordicsemi/android/hrs/service/HRSService.kt b/profile_hrs/src/main/java/no/nordicsemi/android/hrs/service/HRSService.kt index 763ec085..39db85bd 100644 --- a/profile_hrs/src/main/java/no/nordicsemi/android/hrs/service/HRSService.kt +++ b/profile_hrs/src/main/java/no/nordicsemi/android/hrs/service/HRSService.kt @@ -4,7 +4,10 @@ import dagger.hilt.android.AndroidEntryPoint import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach import no.nordicsemi.android.hrs.data.HRSRepository +import no.nordicsemi.android.service.BleManagerStatus +import no.nordicsemi.android.service.BleServiceStatus import no.nordicsemi.android.service.ForegroundBleService +import no.nordicsemi.android.utils.exhaustive import javax.inject.Inject @AndroidEntryPoint @@ -19,7 +22,11 @@ internal class HRSService : ForegroundBleService() { super.onCreate() status.onEach { - repository.setNewStatus(it) + val status = it.mapToSimpleManagerStatus() + repository.setNewStatus(status) + if (status == BleManagerStatus.DISCONNECTED) { + scope.close() + } }.launchIn(scope) repository.command.onEach { diff --git a/profile_hts/src/main/java/no/nordicsemi/android/hts/repository/HTSService.kt b/profile_hts/src/main/java/no/nordicsemi/android/hts/repository/HTSService.kt index 6f8d5b0a..65b91ae1 100644 --- a/profile_hts/src/main/java/no/nordicsemi/android/hts/repository/HTSService.kt +++ b/profile_hts/src/main/java/no/nordicsemi/android/hts/repository/HTSService.kt @@ -4,7 +4,10 @@ import dagger.hilt.android.AndroidEntryPoint import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach import no.nordicsemi.android.hts.data.HTSRepository +import no.nordicsemi.android.service.BleManagerStatus +import no.nordicsemi.android.service.BleServiceStatus import no.nordicsemi.android.service.ForegroundBleService +import no.nordicsemi.android.utils.exhaustive import javax.inject.Inject @AndroidEntryPoint @@ -19,7 +22,11 @@ internal class HTSService : ForegroundBleService() { super.onCreate() status.onEach { - repository.setNewStatus(it) + val status = it.mapToSimpleManagerStatus() + repository.setNewStatus(status) + if (status == BleManagerStatus.DISCONNECTED) { + scope.close() + } }.launchIn(scope) repository.command.onEach { diff --git a/profile_prx/src/main/java/no/nordicsemi/android/prx/data/PRXData.kt b/profile_prx/src/main/java/no/nordicsemi/android/prx/data/PRXData.kt index fe3d1f4c..8e607e8c 100644 --- a/profile_prx/src/main/java/no/nordicsemi/android/prx/data/PRXData.kt +++ b/profile_prx/src/main/java/no/nordicsemi/android/prx/data/PRXData.kt @@ -3,7 +3,8 @@ package no.nordicsemi.android.prx.data internal data class PRXData( val batteryLevel: Int = 0, val localAlarmLevel: AlarmLevel = AlarmLevel.NONE, - val isRemoteAlarm: Boolean = false + val isRemoteAlarm: Boolean = false, + val linkLossAlarmLevel: AlarmLevel = AlarmLevel.HIGH ) internal enum class AlarmLevel(val value: Int) { diff --git a/profile_prx/src/main/java/no/nordicsemi/android/prx/data/PRXRepository.kt b/profile_prx/src/main/java/no/nordicsemi/android/prx/data/PRXRepository.kt index e4b3bff3..eef232d3 100644 --- a/profile_prx/src/main/java/no/nordicsemi/android/prx/data/PRXRepository.kt +++ b/profile_prx/src/main/java/no/nordicsemi/android/prx/data/PRXRepository.kt @@ -27,16 +27,22 @@ internal class PRXRepository @Inject constructor() { _data.tryEmit(_data.value.copy(localAlarmLevel = alarmLevel)) } + fun setLocalAlarmLevel(alarmLevel: AlarmLevel) { + _data.tryEmit(_data.value.copy(localAlarmLevel = alarmLevel)) + } + + fun setLinkLossLevel(value: Int) { + val alarmLevel = AlarmLevel.create(value) + _data.tryEmit(_data.value.copy(linkLossAlarmLevel = alarmLevel)) + } + fun setRemoteAlarmLevel(isOn: Boolean) { _data.tryEmit(_data.value.copy(isRemoteAlarm = isOn)) } fun invokeCommand(command: PRXCommand) { - if (_command.subscriptionCount.value > 0) { - _command.tryEmit(command) - } else { - _status.tryEmit(BleManagerStatus.DISCONNECTED) - } + _command.tryEmit(command) + _status.tryEmit(BleManagerStatus.DISCONNECTED) } fun setNewStatus(status: BleManagerStatus) { diff --git a/profile_prx/src/main/java/no/nordicsemi/android/prx/repository/AlarmHandler.kt b/profile_prx/src/main/java/no/nordicsemi/android/prx/repository/AlarmHandler.kt index 8a1aedb2..27cf81d6 100644 --- a/profile_prx/src/main/java/no/nordicsemi/android/prx/repository/AlarmHandler.kt +++ b/profile_prx/src/main/java/no/nordicsemi/android/prx/repository/AlarmHandler.kt @@ -7,10 +7,12 @@ import android.media.RingtoneManager import android.util.Log import androidx.lifecycle.LifecycleService import dagger.hilt.android.qualifiers.ApplicationContext +import no.nordicsemi.android.prx.data.AlarmLevel import java.io.IOException +import java.lang.Exception import javax.inject.Inject -class AlarmHandler @Inject constructor( +internal class AlarmHandler @Inject constructor( @ApplicationContext private val context: Context ) { @@ -34,28 +36,39 @@ class AlarmHandler @Inject constructor( } } - fun playAlarm() { + fun playAlarm(alarmLevel: AlarmLevel) { val am = context.getSystemService(LifecycleService.AUDIO_SERVICE) as AudioManager originalVolume = am.getStreamVolume(AudioManager.STREAM_ALARM) + + val soundLevel = when (alarmLevel) { + AlarmLevel.NONE -> 0 + AlarmLevel.MEDIUM -> originalVolume / 2 + AlarmLevel.HIGH -> originalVolume + } + am.setStreamVolume( AudioManager.STREAM_ALARM, - am.getStreamMaxVolume(AudioManager.STREAM_ALARM), + soundLevel, AudioManager.FLAG_REMOVE_SOUND_AND_VIBRATE ) try { mediaPlayer.prepare() mediaPlayer.start() - } catch (e: IOException) { + } catch (e: Exception) { Log.e(TAG, "Prepare Alarm failed: ", e) } } fun pauseAlarm() { - if (mediaPlayer.isPlaying) { - mediaPlayer.stop() - // Restore original volume - val am = context.getSystemService(Context.AUDIO_SERVICE) as AudioManager - am.setStreamVolume(AudioManager.STREAM_ALARM, originalVolume, 0) + try { + if (mediaPlayer.isPlaying) { + mediaPlayer.stop() + // Restore original volume + val am = context.getSystemService(Context.AUDIO_SERVICE) as AudioManager + am.setStreamVolume(AudioManager.STREAM_ALARM, originalVolume, 0) + } + } catch (e: Exception) { + Log.e(TAG, "Prepare Alarm failed: ", e) } } diff --git a/profile_prx/src/main/java/no/nordicsemi/android/prx/repository/PRXManager.kt b/profile_prx/src/main/java/no/nordicsemi/android/prx/repository/PRXManager.kt index b9e8ed75..255b05f8 100644 --- a/profile_prx/src/main/java/no/nordicsemi/android/prx/repository/PRXManager.kt +++ b/profile_prx/src/main/java/no/nordicsemi/android/prx/repository/PRXManager.kt @@ -55,6 +55,7 @@ internal class PRXManager( // Server characteristics. private var localAlertLevelCharacteristic: BluetoothGattCharacteristic? = null + private var linkLossServerCharacteristic: BluetoothGattCharacteristic? = null /** * Returns true if the alert has been enabled on the proximity tag, false otherwise. */ @@ -77,8 +78,16 @@ internal class PRXManager( dataHolder.setLocalAlarmLevel(level) } }) + + setWriteCallback(linkLossServerCharacteristic) + .with(object : AlertLevelDataCallback() { + override fun onAlertLevelChanged(device: BluetoothDevice, level: Int) { + dataHolder.setLinkLossLevel(level) + } + }) + // After connection, set the Link Loss behaviour on the tag. - writeCharacteristic(linkLossCharacteristic, AlertLevelData.highAlert()) + writeCharacteristic(linkLossCharacteristic, AlertLevelData.highAlert(), BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT) .done { device: BluetoothDevice? -> log( Log.INFO, @@ -97,9 +106,11 @@ internal class PRXManager( override fun onServerReady(server: BluetoothGattServer) { val immediateAlertService = server.getService(PRX_SERVICE_UUID) if (immediateAlertService != null) { - localAlertLevelCharacteristic = immediateAlertService.getCharacteristic( - ALERT_LEVEL_CHARACTERISTIC_UUID - ) + localAlertLevelCharacteristic = immediateAlertService.getCharacteristic(ALERT_LEVEL_CHARACTERISTIC_UUID) + } + val linkLossService = server.getService(LINK_LOSS_SERVICE_UUID) + if (linkLossService != null) { + linkLossServerCharacteristic = linkLossService.getCharacteristic(ALERT_LEVEL_CHARACTERISTIC_UUID) } } @@ -108,8 +119,7 @@ internal class PRXManager( override fun isRequiredServiceSupported(gatt: BluetoothGatt): Boolean { val llService = gatt.getService(LINK_LOSS_SERVICE_UUID) if (llService != null) { - linkLossCharacteristic = - llService.getCharacteristic(ALERT_LEVEL_CHARACTERISTIC_UUID) + linkLossCharacteristic = llService.getCharacteristic(ALERT_LEVEL_CHARACTERISTIC_UUID) } return linkLossCharacteristic != null } @@ -130,6 +140,7 @@ internal class PRXManager( alertLevelCharacteristic = null linkLossCharacteristic = null localAlertLevelCharacteristic = null + linkLossServerCharacteristic = null // Reset the alert flag isAlertEnabled = false } diff --git a/profile_prx/src/main/java/no/nordicsemi/android/prx/repository/PRXService.kt b/profile_prx/src/main/java/no/nordicsemi/android/prx/repository/PRXService.kt index 9f50c1bb..79c5c853 100644 --- a/profile_prx/src/main/java/no/nordicsemi/android/prx/repository/PRXService.kt +++ b/profile_prx/src/main/java/no/nordicsemi/android/prx/repository/PRXService.kt @@ -1,5 +1,6 @@ package no.nordicsemi.android.prx.repository +import android.util.Log import dagger.hilt.android.AndroidEntryPoint import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach @@ -8,6 +9,8 @@ import no.nordicsemi.android.prx.data.DisableAlarm import no.nordicsemi.android.prx.data.Disconnect import no.nordicsemi.android.prx.data.EnableAlarm import no.nordicsemi.android.prx.data.PRXRepository +import no.nordicsemi.android.service.BleManagerStatus +import no.nordicsemi.android.service.BleServiceStatus import no.nordicsemi.android.service.ForegroundBleService import no.nordicsemi.android.utils.exhaustive import javax.inject.Inject @@ -35,7 +38,20 @@ internal class PRXService : ForegroundBleService() { serverManager.open() status.onEach { - repository.setNewStatus(it) + val bleStatus = when (it) { + BleServiceStatus.CONNECTING -> BleManagerStatus.CONNECTING + BleServiceStatus.OK -> BleManagerStatus.OK + BleServiceStatus.DISCONNECTED -> { + scope.close() + BleManagerStatus.DISCONNECTED + } + BleServiceStatus.LINK_LOSS -> null + }.exhaustive + bleStatus?.let { repository.setNewStatus(it) } + + if (BleServiceStatus.LINK_LOSS == it) { + repository.setLocalAlarmLevel(repository.data.value.linkLossAlarmLevel) + } }.launchIn(scope) repository.command.onEach { @@ -48,13 +64,17 @@ internal class PRXService : ForegroundBleService() { repository.data.onEach { if (it.localAlarmLevel != AlarmLevel.NONE) { - alarmHandler.playAlarm() + alarmHandler.playAlarm(it.localAlarmLevel) } else { alarmHandler.pauseAlarm() } }.launchIn(scope) } + override fun shouldAutoConnect(): Boolean { + return true + } + override fun onDestroy() { super.onDestroy() alarmHandler.releaseAlarm() diff --git a/profile_rscs/src/main/java/no/nordicsemi/android/rscs/repository/RSCSService.kt b/profile_rscs/src/main/java/no/nordicsemi/android/rscs/repository/RSCSService.kt index 38a3223d..a54763bc 100644 --- a/profile_rscs/src/main/java/no/nordicsemi/android/rscs/repository/RSCSService.kt +++ b/profile_rscs/src/main/java/no/nordicsemi/android/rscs/repository/RSCSService.kt @@ -4,7 +4,10 @@ import dagger.hilt.android.AndroidEntryPoint import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach import no.nordicsemi.android.rscs.data.RSCSRepository +import no.nordicsemi.android.service.BleManagerStatus +import no.nordicsemi.android.service.BleServiceStatus import no.nordicsemi.android.service.ForegroundBleService +import no.nordicsemi.android.utils.exhaustive import javax.inject.Inject @AndroidEntryPoint @@ -19,7 +22,11 @@ internal class RSCSService : ForegroundBleService() { super.onCreate() status.onEach { - repository.setNewStatus(it) + val status = it.mapToSimpleManagerStatus() + repository.setNewStatus(status) + if (status == BleManagerStatus.DISCONNECTED) { + scope.close() + } }.launchIn(scope) repository.command.onEach { diff --git a/profile_uart/src/main/java/no/nordicsemi/android/uart/repository/UARTService.kt b/profile_uart/src/main/java/no/nordicsemi/android/uart/repository/UARTService.kt index 19301c10..be2f6f26 100644 --- a/profile_uart/src/main/java/no/nordicsemi/android/uart/repository/UARTService.kt +++ b/profile_uart/src/main/java/no/nordicsemi/android/uart/repository/UARTService.kt @@ -3,6 +3,7 @@ package no.nordicsemi.android.uart.repository import dagger.hilt.android.AndroidEntryPoint import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach +import no.nordicsemi.android.service.BleManagerStatus import no.nordicsemi.android.service.ForegroundBleService import no.nordicsemi.android.uart.data.DisconnectCommand import no.nordicsemi.android.uart.data.SendTextCommand @@ -22,7 +23,11 @@ internal class UARTService : ForegroundBleService() { super.onCreate() status.onEach { - repository.setNewStatus(it) + val status = it.mapToSimpleManagerStatus() + repository.setNewStatus(status) + if (status == BleManagerStatus.DISCONNECTED) { + scope.close() + } }.launchIn(scope) repository.command.onEach {