From 1ae259fc6559e751df35b2adfefffd4fd421dd60 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sylwester=20Zieli=C5=84ski?= Date: Tue, 15 Feb 2022 09:46:45 +0100 Subject: [PATCH] Fix manager in progress indicator --- .../android/nrftoolbox/view/FeatureButton.kt | 37 +++---------- .../android/service/BleManagerStatus.kt | 15 +++-- .../service/ConnectionObserverAdapter.kt | 18 ++++-- .../view/scanner/DeviceDisconnectedView.kt | 3 +- lib_theme/src/main/res/values/strings.xml | 5 +- .../java/no/nordicsemi/android/utils/Ext.kt | 3 + .../nordicsemi/android/bps/view/BPSScreen.kt | 4 +- profile_bps/src/main/res/values/strings.xml | 2 +- .../android/cgms/data/CGMRepository.kt | 12 +--- .../android/cgms/repository/CGMManager.kt | 55 +++++++------------ .../android/cgms/repository/CGMService.kt | 6 ++ .../nordicsemi/android/cgms/view/CGMScreen.kt | 4 +- .../cgms/viewmodel/CGMScreenViewModel.kt | 12 ++-- .../android/csc/data/CSCRepository.kt | 12 +--- .../android/csc/repository/CSCService.kt | 6 ++ .../nordicsemi/android/csc/view/CSCScreen.kt | 4 +- .../android/csc/viewmodel/CSCViewModel.kt | 12 ++-- .../nordicsemi/android/gls/data/GLSManager.kt | 2 +- .../android/gls/main/view/GLSScreen.kt | 4 +- .../android/hrs/data/HRSRepository.kt | 12 +--- .../android/hrs/service/HRSManager.kt | 3 - .../android/hrs/service/HRSService.kt | 6 ++ .../nordicsemi/android/hrs/view/HRSScreen.kt | 4 +- .../android/hrs/viewmodel/HRSViewModel.kt | 12 ++-- .../android/hts/data/HTSRepository.kt | 12 +--- .../android/hts/repository/HTSService.kt | 6 ++ .../nordicsemi/android/hts/view/HTSScreen.kt | 4 +- .../android/hts/viewmodel/HTSViewModel.kt | 14 ++--- profile_hts/src/main/res/values/strings.xml | 2 +- .../android/prx/data/PRXRepository.kt | 12 +--- .../android/prx/repository/PRXService.kt | 6 ++ .../nordicsemi/android/prx/view/PRXScreen.kt | 4 +- .../android/prx/viewmodel/PRXViewModel.kt | 12 ++-- profile_prx/src/main/res/values/strings.xml | 2 +- .../android/rscs/data/RSCSRepository.kt | 12 +--- .../android/rscs/repository/RSCSManager.kt | 11 +++- .../android/rscs/repository/RSCSService.kt | 5 +- .../android/rscs/view/RSCSScreen.kt | 9 +-- .../android/rscs/viewmodel/RSCSViewModel.kt | 12 ++-- .../android/uart/data/UARTRepository.kt | 12 +--- .../android/uart/repository/UARTManager.kt | 4 +- .../android/uart/repository/UARTService.kt | 6 ++ .../android/uart/view/UARTScreen.kt | 9 +-- .../android/uart/viewmodel/UARTViewModel.kt | 12 ++-- 44 files changed, 195 insertions(+), 224 deletions(-) diff --git a/app/src/main/java/no/nordicsemi/android/nrftoolbox/view/FeatureButton.kt b/app/src/main/java/no/nordicsemi/android/nrftoolbox/view/FeatureButton.kt index 811699e2..7f82081f 100644 --- a/app/src/main/java/no/nordicsemi/android/nrftoolbox/view/FeatureButton.kt +++ b/app/src/main/java/no/nordicsemi/android/nrftoolbox/view/FeatureButton.kt @@ -6,16 +6,12 @@ import androidx.compose.foundation.Image import androidx.compose.foundation.background import androidx.compose.foundation.layout.* import androidx.compose.foundation.shape.CircleShape -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.PlayArrow -import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip -import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.ColorFilter import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.res.colorResource @@ -41,6 +37,12 @@ fun FeatureButton( verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.SpaceBetween ) { + val color = if (isRunning == true) { + colorResource(id = R.color.nordicGrass) + } else { + MaterialTheme.colorScheme.secondary + } + Image( painter = painterResource(iconId), contentDescription = stringResource(id = name), @@ -49,7 +51,7 @@ fun FeatureButton( modifier = Modifier .size(64.dp) .clip(CircleShape) - .background(MaterialTheme.colorScheme.secondary) + .background(color) .padding(16.dp) ) @@ -61,35 +63,10 @@ fun FeatureButton( modifier = Modifier.weight(1f), textAlign = TextAlign.Center ) - - Spacer(modifier = Modifier.size(16.dp)) - -// Text( -// text = stringResource(id = nameCode), -// style = MaterialTheme.typography.headlineSmall, -// textAlign = TextAlign.Center -// ) - - isRunning?.let { - Icon( - painter = painterResource(id = R.drawable.ic_running_indicator), - contentDescription = stringResource(id = R.string.running_profile_icon), - tint = getRunningIndicatorColor(it) - ) - } } } } -@Composable -private fun getRunningIndicatorColor(isRunning: Boolean): Color { - return if (isRunning) { - colorResource(id = R.color.nordicGrass) - } else { - MaterialTheme.colorScheme.outline - } -} - @Preview @Composable private fun FeatureButtonPreview() { 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 7186abe9..7ed46832 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 @@ -4,14 +4,21 @@ enum class BleManagerStatus { CONNECTING, OK, LINK_LOSS, DISCONNECTED, MISSING_SERVICE } -sealed class BleManagerResult +sealed class BleManagerResult { + + fun isRunning(): Boolean { + return this is SuccessResult + } + + fun hasBeenDisconnected(): Boolean { + return this is LinkLossResult || this is DisconnectedResult || this is MissingServiceResult + } +} class ConnectingResult : BleManagerResult() -class ReadyResult : BleManagerResult() - data class SuccessResult(val data: T) : BleManagerResult() class LinkLossResult : BleManagerResult() class DisconnectedResult : BleManagerResult() +class UnknownErrorResult : BleManagerResult() class MissingServiceResult : BleManagerResult() - diff --git a/lib_service/src/main/java/no/nordicsemi/android/service/ConnectionObserverAdapter.kt b/lib_service/src/main/java/no/nordicsemi/android/service/ConnectionObserverAdapter.kt index 2b76f84d..b1fe6490 100644 --- a/lib_service/src/main/java/no/nordicsemi/android/service/ConnectionObserverAdapter.kt +++ b/lib_service/src/main/java/no/nordicsemi/android/service/ConnectionObserverAdapter.kt @@ -13,18 +13,20 @@ class ConnectionObserverAdapter : ConnectionObserver { private val _status = MutableStateFlow>(ConnectingResult()) val status = _status.asStateFlow() + private var lastValue: T? = null + override fun onDeviceConnecting(device: BluetoothDevice) { Log.d(TAG, "onDeviceConnecting()") } override fun onDeviceConnected(device: BluetoothDevice) { Log.d(TAG, "onDeviceConnected()") - _status.value = ReadyResult() + _status.value = SuccessResult(lastValue!!) } override fun onDeviceFailedToConnect(device: BluetoothDevice, reason: Int) { - Log.d(TAG, "onDeviceFailedToConnect()") - _status.value = DisconnectedResult() + Log.d(TAG, "onDeviceFailedToConnect(), reason: $reason") + _status.value = MissingServiceResult() } override fun onDeviceReady(device: BluetoothDevice) { @@ -36,15 +38,19 @@ class ConnectionObserverAdapter : ConnectionObserver { } override fun onDeviceDisconnected(device: BluetoothDevice, reason: Int) { - Log.d(TAG, "onDeviceDisconnected()") + Log.d(TAG, "onDeviceDisconnected(), reason: $reason") _status.value = when (reason) { ConnectionObserver.REASON_NOT_SUPPORTED -> MissingServiceResult() ConnectionObserver.REASON_LINK_LOSS -> LinkLossResult() - else -> DisconnectedResult() + ConnectionObserver.REASON_SUCCESS -> DisconnectedResult() + else -> UnknownErrorResult() } } fun setValue(value: T) { - _status.value = SuccessResult(value) + lastValue = value + if (_status.value.isRunning()) { + _status.value = SuccessResult(value) + } } } diff --git a/lib_theme/src/main/java/no/nordicsemi/android/theme/view/scanner/DeviceDisconnectedView.kt b/lib_theme/src/main/java/no/nordicsemi/android/theme/view/scanner/DeviceDisconnectedView.kt index 577aac1c..fd38db6f 100644 --- a/lib_theme/src/main/java/no/nordicsemi/android/theme/view/scanner/DeviceDisconnectedView.kt +++ b/lib_theme/src/main/java/no/nordicsemi/android/theme/view/scanner/DeviceDisconnectedView.kt @@ -20,7 +20,7 @@ import no.nordicsemi.android.theme.R import no.nordicsemi.android.theme.view.ScreenSection enum class Reason { - USER, LINK_LOSS, MISSING_SERVICE + USER, UNKNOWN, LINK_LOSS, MISSING_SERVICE } @Composable @@ -57,6 +57,7 @@ fun DeviceDisconnectedView(reason: Reason, navigateUp: () -> Unit) { Reason.USER -> stringResource(id = R.string.device_reason_user) Reason.LINK_LOSS -> stringResource(id = R.string.device_reason_link_loss) Reason.MISSING_SERVICE -> stringResource(id = R.string.device_reason_missing_service) + Reason.UNKNOWN -> stringResource(id = R.string.device_reason_unknown) } Text( diff --git a/lib_theme/src/main/res/values/strings.xml b/lib_theme/src/main/res/values/strings.xml index cee533f9..a0d9108e 100644 --- a/lib_theme/src/main/res/values/strings.xml +++ b/lib_theme/src/main/res/values/strings.xml @@ -5,7 +5,7 @@ Dialog CANCEL - Go up + Back Close the application. Close the current screen. @@ -15,10 +15,11 @@ Disconnected Device disconnected successfully. + Device disconnected with unknown reason. Device signal has been lost. Device was disconnected, because required services are missing. - Connecting + Connecting... The mobile is trying to connect to peripheral device. Please wait... \ No newline at end of file diff --git a/lib_utils/src/main/java/no/nordicsemi/android/utils/Ext.kt b/lib_utils/src/main/java/no/nordicsemi/android/utils/Ext.kt index ca3239b8..faedf868 100644 --- a/lib_utils/src/main/java/no/nordicsemi/android/utils/Ext.kt +++ b/lib_utils/src/main/java/no/nordicsemi/android/utils/Ext.kt @@ -9,6 +9,9 @@ import androidx.navigation.NavController import kotlinx.coroutines.CoroutineExceptionHandler import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Job +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.launch import no.nordicsemi.android.navigation.ParcelableArgument import no.nordicsemi.android.navigation.SuccessDestinationResult diff --git a/profile_bps/src/main/java/no/nordicsemi/android/bps/view/BPSScreen.kt b/profile_bps/src/main/java/no/nordicsemi/android/bps/view/BPSScreen.kt index 850b75ea..f72bc486 100644 --- a/profile_bps/src/main/java/no/nordicsemi/android/bps/view/BPSScreen.kt +++ b/profile_bps/src/main/java/no/nordicsemi/android/bps/view/BPSScreen.kt @@ -34,11 +34,11 @@ fun BPSScreen() { when (state) { NoDeviceState -> NoDeviceView() is WorkingState -> when (state.result) { - is ConnectingResult, - is ReadyResult -> DeviceConnectingView { viewModel.onEvent(DisconnectEvent) } + is ConnectingResult -> DeviceConnectingView { viewModel.onEvent(DisconnectEvent) } is DisconnectedResult -> DeviceDisconnectedView(Reason.USER, navigateUp) is LinkLossResult -> DeviceDisconnectedView(Reason.LINK_LOSS, navigateUp) is MissingServiceResult -> DeviceDisconnectedView(Reason.MISSING_SERVICE, navigateUp) + is UnknownErrorResult -> DeviceDisconnectedView(Reason.UNKNOWN, navigateUp) is SuccessResult -> BPSContentView(state.result.data) { viewModel.onEvent(it) } } }.exhaustive diff --git a/profile_bps/src/main/res/values/strings.xml b/profile_bps/src/main/res/values/strings.xml index 76e596d9..2da74534 100644 --- a/profile_bps/src/main/res/values/strings.xml +++ b/profile_bps/src/main/res/values/strings.xml @@ -2,7 +2,7 @@ Blood pressure - Records + Data Systolic Diastolic diff --git a/profile_cgms/src/main/java/no/nordicsemi/android/cgms/data/CGMRepository.kt b/profile_cgms/src/main/java/no/nordicsemi/android/cgms/data/CGMRepository.kt index b42a83b9..087dcda7 100644 --- a/profile_cgms/src/main/java/no/nordicsemi/android/cgms/data/CGMRepository.kt +++ b/profile_cgms/src/main/java/no/nordicsemi/android/cgms/data/CGMRepository.kt @@ -4,10 +4,7 @@ import android.bluetooth.BluetoothDevice import android.content.Context import dagger.hilt.android.qualifiers.ApplicationContext import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.asStateFlow -import kotlinx.coroutines.flow.launchIn -import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.flow.* import kotlinx.coroutines.launch import no.nordicsemi.android.ble.ktx.suspend import no.nordicsemi.android.cgms.repository.CGMManager @@ -30,8 +27,8 @@ class CGMRepository @Inject constructor( private val _data = MutableStateFlow>(ConnectingResult()) internal val data = _data.asStateFlow() - private val _isRunning = MutableStateFlow(false) - val isRunning = _isRunning.asStateFlow() + val isRunning = data.map { it.isRunning() } + val hasBeenDisconnected = data.map { it.hasBeenDisconnected() } fun launch(device: BluetoothDevice) { serviceManager.startService(CGMService::class.java, device) @@ -56,7 +53,6 @@ class CGMRepository @Inject constructor( .useAutoConnect(false) .retry(3, 100) .suspend() - _isRunning.value = true } catch (e: Exception) { e.printStackTrace() } @@ -75,9 +71,7 @@ class CGMRepository @Inject constructor( } fun release() { - serviceManager.stopService(CGMService::class.java) manager?.disconnect()?.enqueue() manager = null - _isRunning.value = false } } diff --git a/profile_cgms/src/main/java/no/nordicsemi/android/cgms/repository/CGMManager.kt b/profile_cgms/src/main/java/no/nordicsemi/android/cgms/repository/CGMManager.kt index 50779e2f..fd8b1819 100644 --- a/profile_cgms/src/main/java/no/nordicsemi/android/cgms/repository/CGMManager.kt +++ b/profile_cgms/src/main/java/no/nordicsemi/android/cgms/repository/CGMManager.kt @@ -26,7 +26,7 @@ import android.bluetooth.BluetoothGattCharacteristic import android.content.Context import android.util.Log import android.util.SparseArray -import kotlinx.coroutines.* +import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach @@ -48,6 +48,7 @@ import no.nordicsemi.android.cgms.data.CGMData import no.nordicsemi.android.cgms.data.CGMRecord import no.nordicsemi.android.cgms.data.RequestStatus import no.nordicsemi.android.service.ConnectionObserverAdapter +import no.nordicsemi.android.utils.launchWithCatch import java.util.* val CGMS_SERVICE_UUID: UUID = UUID.fromString("0000181F-0000-1000-8000-00805f9b34fb") @@ -80,10 +81,6 @@ internal class CGMManager( private var sessionStartTime: Long = 0 - private val exceptionHandler = CoroutineExceptionHandler { _, t -> - Log.e("COROUTINE-EXCEPTION", "Uncaught exception", t) - } - private val data = MutableStateFlow(CGMData()) val dataHolder = ConnectionObserverAdapter() @@ -101,32 +98,17 @@ internal class CGMManager( override fun log(priority: Int, message: String) { super.log(priority, message) - Log.d("COROUTINE-EXCEPTION", message) + Log.d("CGM-PROFILE", message) } private inner class CGMManagerGattCallback : BleManagerGattCallback() { override fun initialize() { super.initialize() - scope.launch(exceptionHandler) { - val response = - readCharacteristic(cgmFeatureCharacteristic).suspendForValidResponse() - this@CGMManager.secured = response.features.e2eCrcSupported - } - - scope.launch(exceptionHandler) { - val response = - readCharacteristic(cgmFeatureCharacteristic).suspendForValidResponse() - this@CGMManager.secured = response.features.e2eCrcSupported - } - - scope.launch(exceptionHandler) { - val response = - readCharacteristic(cgmStatusCharacteristic).suspendForValidResponse() - if (response.status?.sessionStopped == false) { - sessionStartTime = System.currentTimeMillis() - response.timeOffset * 60000L - } - } + enableNotifications(cgmMeasurementCharacteristic).enqueue() + enableIndications(cgmSpecificOpsControlPointCharacteristic).enqueue() + enableIndications(recordAccessControlPointCharacteristic).enqueue() + enableNotifications(batteryLevelCharacteristic).enqueue() setNotificationCallback(cgmMeasurementCharacteristic).asValidResponseFlow() .onEach { @@ -164,7 +146,7 @@ internal class CGMManager( setIndicationCallback(recordAccessControlPointCharacteristic).asValidResponseFlow() .onEach { - if (it.isOperationCompleted && !it.wereRecordsFound() && it.numberOfRecords > 0) { + if (it.isOperationCompleted && it.wereRecordsFound() && it.numberOfRecords > 0) { onRecordsReceived(it) } else if (it.isOperationCompleted && !it.wereRecordsFound()) { onNoRecordsFound() @@ -180,13 +162,16 @@ internal class CGMManager( data.value = data.value.copy(batteryLevel = it.batteryLevel) }.launchIn(scope) - enableNotifications(cgmMeasurementCharacteristic).enqueue() - enableIndications(cgmSpecificOpsControlPointCharacteristic).enqueue() - enableIndications(recordAccessControlPointCharacteristic).enqueue() - enableNotifications(batteryLevelCharacteristic).enqueue() + scope.launchWithCatch { + val cgmResponse = readCharacteristic(cgmFeatureCharacteristic).suspendForValidResponse() + this@CGMManager.secured = cgmResponse.features.e2eCrcSupported - if (sessionStartTime == 0L) { - scope.launch(exceptionHandler) { + val response = readCharacteristic(cgmStatusCharacteristic).suspendForValidResponse() + if (response.status?.sessionStopped == false) { + sessionStartTime = System.currentTimeMillis() - response.timeOffset * 60000L + } + + if (sessionStartTime == 0L) { writeCharacteristic( cgmSpecificOpsControlPointCharacteristic, CGMSpecificOpsControlPointData.startSession(secured), @@ -282,7 +267,7 @@ internal class CGMManager( clear() data.value = data.value.copy(requestStatus = RequestStatus.PENDING) recordAccessRequestInProgress = true - scope.launch(exceptionHandler) { + scope.launchWithCatch { writeCharacteristic( recordAccessControlPointCharacteristic, RecordAccessControlPointData.reportLastStoredRecord(), @@ -296,7 +281,7 @@ internal class CGMManager( clear() data.value = data.value.copy(requestStatus = RequestStatus.PENDING) recordAccessRequestInProgress = true - scope.launch(exceptionHandler) { + scope.launchWithCatch { writeCharacteristic( recordAccessControlPointCharacteristic, RecordAccessControlPointData.reportFirstStoredRecord(), @@ -310,7 +295,7 @@ internal class CGMManager( clear() data.value = data.value.copy(requestStatus = RequestStatus.PENDING) recordAccessRequestInProgress = true - scope.launch(exceptionHandler) { + scope.launchWithCatch { writeCharacteristic( recordAccessControlPointCharacteristic, RecordAccessControlPointData.reportNumberOfAllStoredRecords(), 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 73339fc2..f37daa33 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 @@ -4,6 +4,8 @@ import android.bluetooth.BluetoothDevice import android.content.Intent import androidx.lifecycle.lifecycleScope import dagger.hilt.android.AndroidEntryPoint +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach import no.nordicsemi.android.cgms.data.CGMRepository import no.nordicsemi.android.service.DEVICE_DATA import no.nordicsemi.android.service.NotificationService @@ -22,6 +24,10 @@ internal class CGMService : NotificationService() { repository.start(device, lifecycleScope) + repository.hasBeenDisconnected.onEach { + if (it) stopSelf() + }.launchIn(lifecycleScope) + return START_REDELIVER_INTENT } } diff --git a/profile_cgms/src/main/java/no/nordicsemi/android/cgms/view/CGMScreen.kt b/profile_cgms/src/main/java/no/nordicsemi/android/cgms/view/CGMScreen.kt index 048dd298..87541fea 100644 --- a/profile_cgms/src/main/java/no/nordicsemi/android/cgms/view/CGMScreen.kt +++ b/profile_cgms/src/main/java/no/nordicsemi/android/cgms/view/CGMScreen.kt @@ -32,11 +32,11 @@ fun CGMScreen() { when (state) { NoDeviceState -> NoDeviceView() is WorkingState -> when (state.result) { - is ConnectingResult, - is ReadyResult -> DeviceConnectingView { viewModel.onEvent(DisconnectEvent) } + is ConnectingResult -> DeviceConnectingView { viewModel.onEvent(DisconnectEvent) } is DisconnectedResult -> DeviceDisconnectedView(Reason.USER, navigateUp) is LinkLossResult -> DeviceDisconnectedView(Reason.LINK_LOSS, navigateUp) is MissingServiceResult -> DeviceDisconnectedView(Reason.MISSING_SERVICE, navigateUp) + is UnknownErrorResult -> DeviceDisconnectedView(Reason.UNKNOWN, navigateUp) is SuccessResult -> CGMContentView(state.result.data) { viewModel.onEvent(it) } } }.exhaustive diff --git a/profile_cgms/src/main/java/no/nordicsemi/android/cgms/viewmodel/CGMScreenViewModel.kt b/profile_cgms/src/main/java/no/nordicsemi/android/cgms/viewmodel/CGMScreenViewModel.kt index 301daf2c..ac025360 100644 --- a/profile_cgms/src/main/java/no/nordicsemi/android/cgms/viewmodel/CGMScreenViewModel.kt +++ b/profile_cgms/src/main/java/no/nordicsemi/android/cgms/viewmodel/CGMScreenViewModel.kt @@ -3,10 +3,8 @@ package no.nordicsemi.android.cgms.viewmodel import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import dagger.hilt.android.lifecycle.HiltViewModel -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.asStateFlow -import kotlinx.coroutines.flow.launchIn -import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.flow.* +import kotlinx.coroutines.launch import no.nordicsemi.android.cgms.data.CGMRepository import no.nordicsemi.android.cgms.data.CGMServiceCommand import no.nordicsemi.android.cgms.repository.CGMS_SERVICE_UUID @@ -27,8 +25,10 @@ internal class CGMScreenViewModel @Inject constructor( val state = _state.asStateFlow() init { - if (!repository.isRunning.value) { - requestBluetoothDevice() + viewModelScope.launch { + if (repository.isRunning.firstOrNull() == false) { + requestBluetoothDevice() + } } repository.data.onEach { diff --git a/profile_csc/src/main/java/no/nordicsemi/android/csc/data/CSCRepository.kt b/profile_csc/src/main/java/no/nordicsemi/android/csc/data/CSCRepository.kt index 86439fc8..0dd26fcb 100644 --- a/profile_csc/src/main/java/no/nordicsemi/android/csc/data/CSCRepository.kt +++ b/profile_csc/src/main/java/no/nordicsemi/android/csc/data/CSCRepository.kt @@ -4,10 +4,7 @@ import android.bluetooth.BluetoothDevice import android.content.Context import dagger.hilt.android.qualifiers.ApplicationContext import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.asStateFlow -import kotlinx.coroutines.flow.launchIn -import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.flow.* import kotlinx.coroutines.launch import no.nordicsemi.android.ble.ktx.suspend import no.nordicsemi.android.csc.repository.CSCManager @@ -29,8 +26,8 @@ class CSCRepository @Inject constructor( private val _data = MutableStateFlow>(ConnectingResult()) internal val data = _data.asStateFlow() - private val _isRunning = MutableStateFlow(false) - val isRunning = _isRunning.asStateFlow() + val isRunning = data.map { it.isRunning() } + val hasBeenDisconnected = data.map { it.hasBeenDisconnected() } fun launch(device: BluetoothDevice) { serviceManager.startService(CSCService::class.java, device) @@ -59,16 +56,13 @@ class CSCRepository @Inject constructor( .useAutoConnect(false) .retry(3, 100) .suspend() - _isRunning.value = true } catch (e: Exception) { e.printStackTrace() } } fun release() { - serviceManager.stopService(CSCService::class.java) manager?.disconnect()?.enqueue() manager = null - _isRunning.value = false } } 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 e36cd86a..c26bd5e1 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 @@ -4,6 +4,8 @@ import android.bluetooth.BluetoothDevice import android.content.Intent import androidx.lifecycle.lifecycleScope 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.service.DEVICE_DATA import no.nordicsemi.android.service.NotificationService @@ -22,6 +24,10 @@ internal class CSCService : NotificationService() { repository.start(device, lifecycleScope) + repository.hasBeenDisconnected.onEach { + if (it) stopSelf() + }.launchIn(lifecycleScope) + return START_REDELIVER_INTENT } } diff --git a/profile_csc/src/main/java/no/nordicsemi/android/csc/view/CSCScreen.kt b/profile_csc/src/main/java/no/nordicsemi/android/csc/view/CSCScreen.kt index 6feb3b3b..d281992e 100644 --- a/profile_csc/src/main/java/no/nordicsemi/android/csc/view/CSCScreen.kt +++ b/profile_csc/src/main/java/no/nordicsemi/android/csc/view/CSCScreen.kt @@ -32,11 +32,11 @@ fun CSCScreen() { when (state.cscManagerState) { NoDeviceState -> NoDeviceView() is WorkingState -> when (state.cscManagerState.result) { - is ConnectingResult, - is ReadyResult -> DeviceConnectingView { viewModel.onEvent(OnDisconnectButtonClick) } + is ConnectingResult -> DeviceConnectingView { viewModel.onEvent(OnDisconnectButtonClick) } is DisconnectedResult -> DeviceDisconnectedView(Reason.USER, navigateUp) is LinkLossResult -> DeviceDisconnectedView(Reason.LINK_LOSS, navigateUp) is MissingServiceResult -> DeviceDisconnectedView(Reason.MISSING_SERVICE, navigateUp) + is UnknownErrorResult -> DeviceDisconnectedView(Reason.UNKNOWN, navigateUp) is SuccessResult -> CSCContentView(state.cscManagerState.result.data, state.speedUnit) { viewModel.onEvent(it) } } }.exhaustive diff --git a/profile_csc/src/main/java/no/nordicsemi/android/csc/viewmodel/CSCViewModel.kt b/profile_csc/src/main/java/no/nordicsemi/android/csc/viewmodel/CSCViewModel.kt index 205f8fa5..9ba34051 100644 --- a/profile_csc/src/main/java/no/nordicsemi/android/csc/viewmodel/CSCViewModel.kt +++ b/profile_csc/src/main/java/no/nordicsemi/android/csc/viewmodel/CSCViewModel.kt @@ -3,10 +3,8 @@ package no.nordicsemi.android.csc.viewmodel import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import dagger.hilt.android.lifecycle.HiltViewModel -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.asStateFlow -import kotlinx.coroutines.flow.launchIn -import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.flow.* +import kotlinx.coroutines.launch import no.nordicsemi.android.csc.data.CSCRepository import no.nordicsemi.android.csc.repository.CSC_SERVICE_UUID import no.nordicsemi.android.csc.view.* @@ -26,8 +24,10 @@ internal class CSCViewModel @Inject constructor( val state = _state.asStateFlow() init { - if (!repository.isRunning.value) { - requestBluetoothDevice() + viewModelScope.launch { + if (repository.isRunning.firstOrNull() == false) { + requestBluetoothDevice() + } } repository.data.onEach { diff --git a/profile_gls/src/main/java/no/nordicsemi/android/gls/data/GLSManager.kt b/profile_gls/src/main/java/no/nordicsemi/android/gls/data/GLSManager.kt index 92cf4768..800b60da 100644 --- a/profile_gls/src/main/java/no/nordicsemi/android/gls/data/GLSManager.kt +++ b/profile_gls/src/main/java/no/nordicsemi/android/gls/data/GLSManager.kt @@ -175,7 +175,7 @@ internal class GLSManager @Inject constructor( gatt.getService(BATTERY_SERVICE_UUID)?.run { batteryLevelCharacteristic = getCharacteristic(BATTERY_LEVEL_CHARACTERISTIC_UUID) } - return glucoseMeasurementCharacteristic != null && recordAccessControlPointCharacteristic != null && glucoseMeasurementContextCharacteristic != null && batteryLevelCharacteristic != null + return glucoseMeasurementCharacteristic != null && recordAccessControlPointCharacteristic != null && batteryLevelCharacteristic != null } override fun onServicesInvalidated() {} diff --git a/profile_gls/src/main/java/no/nordicsemi/android/gls/main/view/GLSScreen.kt b/profile_gls/src/main/java/no/nordicsemi/android/gls/main/view/GLSScreen.kt index 7871c368..14cd619c 100644 --- a/profile_gls/src/main/java/no/nordicsemi/android/gls/main/view/GLSScreen.kt +++ b/profile_gls/src/main/java/no/nordicsemi/android/gls/main/view/GLSScreen.kt @@ -34,11 +34,11 @@ fun GLSScreen() { when (state) { NoDeviceState -> NoDeviceView() is WorkingState -> when (state.result) { - is ConnectingResult, - is ReadyResult -> DeviceConnectingView { viewModel.onEvent(DisconnectEvent) } + is ConnectingResult -> DeviceConnectingView { viewModel.onEvent(DisconnectEvent) } is DisconnectedResult -> DeviceDisconnectedView(Reason.USER, navigateUp) is LinkLossResult -> DeviceDisconnectedView(Reason.LINK_LOSS, navigateUp) is MissingServiceResult -> DeviceDisconnectedView(Reason.MISSING_SERVICE, navigateUp) + is UnknownErrorResult -> DeviceDisconnectedView(Reason.UNKNOWN, navigateUp) is SuccessResult -> GLSContentView(state.result.data) { viewModel.onEvent(it) } } }.exhaustive diff --git a/profile_hrs/src/main/java/no/nordicsemi/android/hrs/data/HRSRepository.kt b/profile_hrs/src/main/java/no/nordicsemi/android/hrs/data/HRSRepository.kt index d638d98b..f06184a7 100644 --- a/profile_hrs/src/main/java/no/nordicsemi/android/hrs/data/HRSRepository.kt +++ b/profile_hrs/src/main/java/no/nordicsemi/android/hrs/data/HRSRepository.kt @@ -4,10 +4,7 @@ import android.bluetooth.BluetoothDevice import android.content.Context import dagger.hilt.android.qualifiers.ApplicationContext import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.asStateFlow -import kotlinx.coroutines.flow.launchIn -import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.flow.* import kotlinx.coroutines.launch import no.nordicsemi.android.ble.ktx.suspend import no.nordicsemi.android.hrs.service.HRSManager @@ -29,8 +26,8 @@ class HRSRepository @Inject constructor( private val _data = MutableStateFlow>(ConnectingResult()) internal val data = _data.asStateFlow() - private val _isRunning = MutableStateFlow(false) - val isRunning = _isRunning.asStateFlow() + val isRunning = data.map { it.isRunning() } + val hasBeenDisconnected = data.map { it.hasBeenDisconnected() } fun launch(device: BluetoothDevice) { serviceManager.startService(HRSService::class.java, device) @@ -55,16 +52,13 @@ class HRSRepository @Inject constructor( .useAutoConnect(false) .retry(3, 100) .suspend() - _isRunning.value = true } catch (e: Exception) { e.printStackTrace() } } fun release() { - serviceManager.stopService(HRSService::class.java) manager?.disconnect()?.enqueue() manager = null - _isRunning.value = false } } diff --git a/profile_hrs/src/main/java/no/nordicsemi/android/hrs/service/HRSManager.kt b/profile_hrs/src/main/java/no/nordicsemi/android/hrs/service/HRSManager.kt index de7b7244..2cb555f6 100644 --- a/profile_hrs/src/main/java/no/nordicsemi/android/hrs/service/HRSManager.kt +++ b/profile_hrs/src/main/java/no/nordicsemi/android/hrs/service/HRSManager.kt @@ -24,13 +24,10 @@ package no.nordicsemi.android.hrs.service import android.bluetooth.BluetoothGatt import android.bluetooth.BluetoothGattCharacteristic import android.content.Context -import android.util.Log -import kotlinx.coroutines.CoroutineExceptionHandler import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach -import kotlinx.coroutines.launch import no.nordicsemi.android.ble.BleManager import no.nordicsemi.android.ble.common.callback.battery.BatteryLevelResponse import no.nordicsemi.android.ble.common.callback.hr.BodySensorLocationResponse 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 3de42e08..1502e017 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,6 +4,8 @@ import android.bluetooth.BluetoothDevice import android.content.Intent import androidx.lifecycle.lifecycleScope 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.DEVICE_DATA import no.nordicsemi.android.service.NotificationService @@ -22,6 +24,10 @@ internal class HRSService : NotificationService() { repository.start(device, lifecycleScope) + repository.hasBeenDisconnected.onEach { + if (it) stopSelf() + }.launchIn(lifecycleScope) + return START_REDELIVER_INTENT } } diff --git a/profile_hrs/src/main/java/no/nordicsemi/android/hrs/view/HRSScreen.kt b/profile_hrs/src/main/java/no/nordicsemi/android/hrs/view/HRSScreen.kt index beedf2a7..e9b59190 100644 --- a/profile_hrs/src/main/java/no/nordicsemi/android/hrs/view/HRSScreen.kt +++ b/profile_hrs/src/main/java/no/nordicsemi/android/hrs/view/HRSScreen.kt @@ -32,11 +32,11 @@ fun HRSScreen() { when (state) { NoDeviceState -> NoDeviceView() is WorkingState -> when (state.result) { - is ConnectingResult, - is ReadyResult -> DeviceConnectingView { viewModel.onEvent(DisconnectEvent) } + is ConnectingResult -> DeviceConnectingView { viewModel.onEvent(DisconnectEvent) } is DisconnectedResult -> DeviceDisconnectedView(Reason.USER, navigateUp) is LinkLossResult -> DeviceDisconnectedView(Reason.LINK_LOSS, navigateUp) is MissingServiceResult -> DeviceDisconnectedView(Reason.MISSING_SERVICE, navigateUp) + is UnknownErrorResult -> DeviceDisconnectedView(Reason.UNKNOWN, navigateUp) is SuccessResult -> HRSContentView(state.result.data) { viewModel.onEvent(it) } } }.exhaustive diff --git a/profile_hrs/src/main/java/no/nordicsemi/android/hrs/viewmodel/HRSViewModel.kt b/profile_hrs/src/main/java/no/nordicsemi/android/hrs/viewmodel/HRSViewModel.kt index 6a5ac14c..af64f2ec 100644 --- a/profile_hrs/src/main/java/no/nordicsemi/android/hrs/viewmodel/HRSViewModel.kt +++ b/profile_hrs/src/main/java/no/nordicsemi/android/hrs/viewmodel/HRSViewModel.kt @@ -3,10 +3,8 @@ package no.nordicsemi.android.hrs.viewmodel import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import dagger.hilt.android.lifecycle.HiltViewModel -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.asStateFlow -import kotlinx.coroutines.flow.launchIn -import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.flow.* +import kotlinx.coroutines.launch import no.nordicsemi.android.hrs.data.HRSRepository import no.nordicsemi.android.hrs.service.HRS_SERVICE_UUID import no.nordicsemi.android.hrs.view.* @@ -26,8 +24,10 @@ internal class HRSViewModel @Inject constructor( val state = _state.asStateFlow() init { - if (!repository.isRunning.value) { - requestBluetoothDevice() + viewModelScope.launch { + if (repository.isRunning.firstOrNull() == false) { + requestBluetoothDevice() + } } repository.data.onEach { diff --git a/profile_hts/src/main/java/no/nordicsemi/android/hts/data/HTSRepository.kt b/profile_hts/src/main/java/no/nordicsemi/android/hts/data/HTSRepository.kt index af24c5f6..f837b7a4 100644 --- a/profile_hts/src/main/java/no/nordicsemi/android/hts/data/HTSRepository.kt +++ b/profile_hts/src/main/java/no/nordicsemi/android/hts/data/HTSRepository.kt @@ -4,10 +4,7 @@ import android.bluetooth.BluetoothDevice import android.content.Context import dagger.hilt.android.qualifiers.ApplicationContext import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.asStateFlow -import kotlinx.coroutines.flow.launchIn -import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.flow.* import kotlinx.coroutines.launch import no.nordicsemi.android.ble.ktx.suspend import no.nordicsemi.android.hts.repository.HTSManager @@ -29,8 +26,8 @@ class HTSRepository @Inject constructor( private val _data = MutableStateFlow>(ConnectingResult()) internal val data = _data.asStateFlow() - private val _isRunning = MutableStateFlow(false) - val isRunning = _isRunning.asStateFlow() + val isRunning = data.map { it.isRunning() } + val hasBeenDisconnected = data.map { it.hasBeenDisconnected() } fun launch(device: BluetoothDevice) { serviceManager.startService(HTSService::class.java, device) @@ -55,16 +52,13 @@ class HTSRepository @Inject constructor( .useAutoConnect(false) .retry(3, 100) .suspend() - _isRunning.value = true } catch (e: Exception) { e.printStackTrace() } } fun release() { - serviceManager.stopService(HTSService::class.java) manager?.disconnect()?.enqueue() manager = null - _isRunning.value = false } } 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 a921a0f1..1b4399e5 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,6 +4,8 @@ import android.bluetooth.BluetoothDevice import android.content.Intent import androidx.lifecycle.lifecycleScope 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.DEVICE_DATA import no.nordicsemi.android.service.NotificationService @@ -22,6 +24,10 @@ internal class HTSService : NotificationService() { repository.start(device, lifecycleScope) + repository.hasBeenDisconnected.onEach { + if (it) stopSelf() + }.launchIn(lifecycleScope) + return START_REDELIVER_INTENT } } diff --git a/profile_hts/src/main/java/no/nordicsemi/android/hts/view/HTSScreen.kt b/profile_hts/src/main/java/no/nordicsemi/android/hts/view/HTSScreen.kt index b6ff8cef..0139a260 100644 --- a/profile_hts/src/main/java/no/nordicsemi/android/hts/view/HTSScreen.kt +++ b/profile_hts/src/main/java/no/nordicsemi/android/hts/view/HTSScreen.kt @@ -32,11 +32,11 @@ fun HTSScreen() { when (state.htsManagerState) { NoDeviceState -> NoDeviceView() is WorkingState -> when (state.htsManagerState.result) { - is ConnectingResult, - is ReadyResult -> DeviceConnectingView { viewModel.onEvent(DisconnectEvent) } + is ConnectingResult -> DeviceConnectingView { viewModel.onEvent(DisconnectEvent) } is DisconnectedResult -> DeviceDisconnectedView(Reason.USER, navigateUp) is LinkLossResult -> DeviceDisconnectedView(Reason.LINK_LOSS, navigateUp) is MissingServiceResult -> DeviceDisconnectedView(Reason.MISSING_SERVICE, navigateUp) + is UnknownErrorResult -> DeviceDisconnectedView(Reason.UNKNOWN, navigateUp) is SuccessResult -> HTSContentView(state.htsManagerState.result.data, state.temperatureUnit) { viewModel.onEvent(it) } } }.exhaustive diff --git a/profile_hts/src/main/java/no/nordicsemi/android/hts/viewmodel/HTSViewModel.kt b/profile_hts/src/main/java/no/nordicsemi/android/hts/viewmodel/HTSViewModel.kt index 2743f982..03aee6bd 100644 --- a/profile_hts/src/main/java/no/nordicsemi/android/hts/viewmodel/HTSViewModel.kt +++ b/profile_hts/src/main/java/no/nordicsemi/android/hts/viewmodel/HTSViewModel.kt @@ -3,15 +3,12 @@ package no.nordicsemi.android.hts.viewmodel import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import dagger.hilt.android.lifecycle.HiltViewModel -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.asStateFlow -import kotlinx.coroutines.flow.launchIn -import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.flow.* +import kotlinx.coroutines.launch import no.nordicsemi.android.hts.data.HTSRepository import no.nordicsemi.android.hts.repository.HTS_SERVICE_UUID import no.nordicsemi.android.hts.view.* import no.nordicsemi.android.navigation.* -import no.nordicsemi.android.service.ServiceManager import no.nordicsemi.android.utils.exhaustive import no.nordicsemi.android.utils.getDevice import no.nordicsemi.ui.scanner.ScannerDestinationId @@ -20,7 +17,6 @@ import javax.inject.Inject @HiltViewModel internal class HTSViewModel @Inject constructor( private val repository: HTSRepository, - private val serviceManager: ServiceManager, private val navigationManager: NavigationManager ) : ViewModel() { @@ -28,8 +24,10 @@ internal class HTSViewModel @Inject constructor( val state = _state.asStateFlow() init { - if (!repository.isRunning.value) { - requestBluetoothDevice() + viewModelScope.launch { + if (repository.isRunning.firstOrNull() == false) { + requestBluetoothDevice() + } } repository.data.onEach { diff --git a/profile_hts/src/main/res/values/strings.xml b/profile_hts/src/main/res/values/strings.xml index 4c3ebe91..c4b40992 100644 --- a/profile_hts/src/main/res/values/strings.xml +++ b/profile_hts/src/main/res/values/strings.xml @@ -7,5 +7,5 @@ %.1f °K Temperature - Records + Data 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 7b215213..f2e1be37 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 @@ -4,10 +4,7 @@ import android.bluetooth.BluetoothDevice import android.content.Context import dagger.hilt.android.qualifiers.ApplicationContext import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.asStateFlow -import kotlinx.coroutines.flow.launchIn -import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.flow.* import kotlinx.coroutines.launch import no.nordicsemi.android.ble.ktx.suspend import no.nordicsemi.android.prx.repository.AlarmHandler @@ -35,8 +32,8 @@ class PRXRepository @Inject internal constructor( private val _data = MutableStateFlow>(ConnectingResult()) internal val data = _data.asStateFlow() - private val _isRunning = MutableStateFlow(false) - val isRunning = _isRunning.asStateFlow() + val isRunning = data.map { it.isRunning() } + val hasBeenDisconnected = data.map { it.hasBeenDisconnected() } fun launch(device: BluetoothDevice) { serviceManager.startService(PRXService::class.java, device) @@ -64,7 +61,6 @@ class PRXRepository @Inject internal constructor( .useAutoConnect(false) .retry(3, 100) .suspend() - _isRunning.value = true } catch (e: Exception) { e.printStackTrace() } @@ -89,9 +85,7 @@ class PRXRepository @Inject internal constructor( } fun release() { - serviceManager.stopService(PRXService::class.java) manager?.disconnect()?.enqueue() manager = null - _isRunning.value = 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 98b65b31..b74ba9a3 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 @@ -4,6 +4,8 @@ import android.bluetooth.BluetoothDevice import android.content.Intent import androidx.lifecycle.lifecycleScope import dagger.hilt.android.AndroidEntryPoint +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach import no.nordicsemi.android.prx.data.PRXRepository import no.nordicsemi.android.service.DEVICE_DATA import no.nordicsemi.android.service.NotificationService @@ -22,6 +24,10 @@ internal class PRXService : NotificationService() { repository.start(device, lifecycleScope) + repository.hasBeenDisconnected.onEach { + if (it) stopSelf() + }.launchIn(lifecycleScope) + return START_REDELIVER_INTENT } } diff --git a/profile_prx/src/main/java/no/nordicsemi/android/prx/view/PRXScreen.kt b/profile_prx/src/main/java/no/nordicsemi/android/prx/view/PRXScreen.kt index 2332dacf..11d699e5 100644 --- a/profile_prx/src/main/java/no/nordicsemi/android/prx/view/PRXScreen.kt +++ b/profile_prx/src/main/java/no/nordicsemi/android/prx/view/PRXScreen.kt @@ -33,11 +33,11 @@ fun PRXScreen() { when (state) { NoDeviceState -> NoDeviceView() is WorkingState -> when (state.result) { - is ConnectingResult, - is ReadyResult -> DeviceConnectingView { viewModel.onEvent(DisconnectEvent) } + is ConnectingResult -> DeviceConnectingView { viewModel.onEvent(DisconnectEvent) } is DisconnectedResult -> DeviceDisconnectedView(Reason.USER, navigateUp) is LinkLossResult -> DeviceOutOfRangeView { viewModel.onEvent(DisconnectEvent) } is MissingServiceResult -> DeviceDisconnectedView(Reason.MISSING_SERVICE, navigateUp) + is UnknownErrorResult -> DeviceDisconnectedView(Reason.UNKNOWN, navigateUp) is SuccessResult -> ContentView(state.result.data) { viewModel.onEvent(it) } } }.exhaustive diff --git a/profile_prx/src/main/java/no/nordicsemi/android/prx/viewmodel/PRXViewModel.kt b/profile_prx/src/main/java/no/nordicsemi/android/prx/viewmodel/PRXViewModel.kt index ec4a28cf..7089d213 100644 --- a/profile_prx/src/main/java/no/nordicsemi/android/prx/viewmodel/PRXViewModel.kt +++ b/profile_prx/src/main/java/no/nordicsemi/android/prx/viewmodel/PRXViewModel.kt @@ -3,10 +3,8 @@ package no.nordicsemi.android.prx.viewmodel import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import dagger.hilt.android.lifecycle.HiltViewModel -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.asStateFlow -import kotlinx.coroutines.flow.launchIn -import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.flow.* +import kotlinx.coroutines.launch import no.nordicsemi.android.navigation.* import no.nordicsemi.android.prx.data.PRXRepository import no.nordicsemi.android.prx.repository.PRX_SERVICE_UUID @@ -26,8 +24,10 @@ internal class PRXViewModel @Inject constructor( val state = _state.asStateFlow() init { - if (!repository.isRunning.value) { - requestBluetoothDevice() + viewModelScope.launch { + if (repository.isRunning.firstOrNull() == false) { + requestBluetoothDevice() + } } repository.data.onEach { diff --git a/profile_prx/src/main/res/values/strings.xml b/profile_prx/src/main/res/values/strings.xml index b6114218..fba896bf 100644 --- a/profile_prx/src/main/res/values/strings.xml +++ b/profile_prx/src/main/res/values/strings.xml @@ -5,7 +5,7 @@ SILENT ME FIND ME - Records + Data Settings none diff --git a/profile_rscs/src/main/java/no/nordicsemi/android/rscs/data/RSCSRepository.kt b/profile_rscs/src/main/java/no/nordicsemi/android/rscs/data/RSCSRepository.kt index bbd6eabb..c668e1d2 100644 --- a/profile_rscs/src/main/java/no/nordicsemi/android/rscs/data/RSCSRepository.kt +++ b/profile_rscs/src/main/java/no/nordicsemi/android/rscs/data/RSCSRepository.kt @@ -4,10 +4,7 @@ import android.bluetooth.BluetoothDevice import android.content.Context import dagger.hilt.android.qualifiers.ApplicationContext import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.asStateFlow -import kotlinx.coroutines.flow.launchIn -import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.flow.* import kotlinx.coroutines.launch import no.nordicsemi.android.ble.ktx.suspend import no.nordicsemi.android.rscs.repository.RSCSManager @@ -29,8 +26,8 @@ class RSCSRepository @Inject constructor( private val _data = MutableStateFlow>(ConnectingResult()) internal val data = _data.asStateFlow() - private val _isRunning = MutableStateFlow(false) - val isRunning = _isRunning.asStateFlow() + val isRunning = data.map { it.isRunning() } + val hasBeenDisconnected = data.map { it.hasBeenDisconnected() } fun launch(device: BluetoothDevice) { serviceManager.startService(RSCSService::class.java, device) @@ -55,16 +52,13 @@ class RSCSRepository @Inject constructor( .useAutoConnect(false) .retry(3, 100) .suspend() - _isRunning.value = true } catch (e: Exception) { e.printStackTrace() } } fun release() { - serviceManager.stopService(RSCSService::class.java) manager?.disconnect()?.enqueue() manager = null - _isRunning.value = false } } diff --git a/profile_rscs/src/main/java/no/nordicsemi/android/rscs/repository/RSCSManager.kt b/profile_rscs/src/main/java/no/nordicsemi/android/rscs/repository/RSCSManager.kt index e32e2952..4c0aac5d 100644 --- a/profile_rscs/src/main/java/no/nordicsemi/android/rscs/repository/RSCSManager.kt +++ b/profile_rscs/src/main/java/no/nordicsemi/android/rscs/repository/RSCSManager.kt @@ -29,6 +29,7 @@ import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach import no.nordicsemi.android.ble.BleManager +import no.nordicsemi.android.ble.common.callback.battery.BatteryLevelResponse import no.nordicsemi.android.ble.common.callback.rsc.RunningSpeedAndCadenceMeasurementResponse import no.nordicsemi.android.ble.ktx.asValidResponseFlow import no.nordicsemi.android.ble.ktx.suspend @@ -76,10 +77,14 @@ internal class RSCSManager internal constructor( totalDistance = it.totalDistance )) }.launchIn(scope) + enableNotifications(rscMeasurementCharacteristic).enqueue() - scope.launchWithCatch { - enableNotifications(rscMeasurementCharacteristic).suspend() - } + setNotificationCallback(batteryLevelCharacteristic) + .asValidResponseFlow() + .onEach { + data.value = data.value.copy(batteryLevel = it.batteryLevel) + }.launchIn(scope) + enableNotifications(batteryLevelCharacteristic).enqueue() } public override fun isRequiredServiceSupported(gatt: BluetoothGatt): Boolean { 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 f935b2e5..f2fcb1c5 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 @@ -8,7 +8,6 @@ import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach import no.nordicsemi.android.rscs.data.RSCSRepository import no.nordicsemi.android.service.DEVICE_DATA -import no.nordicsemi.android.service.ForegroundBleService import no.nordicsemi.android.service.NotificationService import javax.inject.Inject @@ -25,6 +24,10 @@ internal class RSCSService : NotificationService() { repository.start(device, lifecycleScope) + repository.hasBeenDisconnected.onEach { + if (it) stopSelf() + }.launchIn(lifecycleScope) + return START_REDELIVER_INTENT } } diff --git a/profile_rscs/src/main/java/no/nordicsemi/android/rscs/view/RSCSScreen.kt b/profile_rscs/src/main/java/no/nordicsemi/android/rscs/view/RSCSScreen.kt index d900ce66..1dcbbe62 100644 --- a/profile_rscs/src/main/java/no/nordicsemi/android/rscs/view/RSCSScreen.kt +++ b/profile_rscs/src/main/java/no/nordicsemi/android/rscs/view/RSCSScreen.kt @@ -26,20 +26,17 @@ fun RSCSScreen() { Column { val navigateUp = { viewModel.onEvent(NavigateUpEvent) } - BackIconAppBar(stringResource(id = R.string.rscs_title)) { - viewModel.onEvent(DisconnectEvent) - } + BackIconAppBar(stringResource(id = R.string.rscs_title), navigateUp) Column(modifier = Modifier.verticalScroll(rememberScrollState())) { when (state) { NoDeviceState -> NoDeviceView() is WorkingState -> when (state.result) { - is ConnectingResult, - is ReadyResult - -> DeviceConnectingView { viewModel.onEvent(DisconnectEvent) } + is ConnectingResult -> DeviceConnectingView { viewModel.onEvent(DisconnectEvent) } is DisconnectedResult -> DeviceDisconnectedView(Reason.USER, navigateUp) is LinkLossResult -> DeviceDisconnectedView(Reason.LINK_LOSS, navigateUp) is MissingServiceResult -> DeviceDisconnectedView(Reason.MISSING_SERVICE, navigateUp) + is UnknownErrorResult -> DeviceDisconnectedView(Reason.UNKNOWN, navigateUp) is SuccessResult -> RSCSContentView(state.result.data) { viewModel.onEvent(it) } } }.exhaustive diff --git a/profile_rscs/src/main/java/no/nordicsemi/android/rscs/viewmodel/RSCSViewModel.kt b/profile_rscs/src/main/java/no/nordicsemi/android/rscs/viewmodel/RSCSViewModel.kt index aa321e58..c4126d80 100644 --- a/profile_rscs/src/main/java/no/nordicsemi/android/rscs/viewmodel/RSCSViewModel.kt +++ b/profile_rscs/src/main/java/no/nordicsemi/android/rscs/viewmodel/RSCSViewModel.kt @@ -3,10 +3,8 @@ package no.nordicsemi.android.rscs.viewmodel import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import dagger.hilt.android.lifecycle.HiltViewModel -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.asStateFlow -import kotlinx.coroutines.flow.launchIn -import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.flow.* +import kotlinx.coroutines.launch import no.nordicsemi.android.navigation.* import no.nordicsemi.android.rscs.data.RSCSRepository import no.nordicsemi.android.rscs.repository.RSCS_SERVICE_UUID @@ -26,8 +24,10 @@ internal class RSCSViewModel @Inject constructor( val state = _state.asStateFlow() init { - if (!repository.isRunning.value) { - requestBluetoothDevice() + viewModelScope.launch { + if (repository.isRunning.firstOrNull() == false) { + requestBluetoothDevice() + } } repository.data.onEach { diff --git a/profile_uart/src/main/java/no/nordicsemi/android/uart/data/UARTRepository.kt b/profile_uart/src/main/java/no/nordicsemi/android/uart/data/UARTRepository.kt index 4c64b7c8..d6bc2aa8 100644 --- a/profile_uart/src/main/java/no/nordicsemi/android/uart/data/UARTRepository.kt +++ b/profile_uart/src/main/java/no/nordicsemi/android/uart/data/UARTRepository.kt @@ -4,10 +4,7 @@ import android.bluetooth.BluetoothDevice import android.content.Context import dagger.hilt.android.qualifiers.ApplicationContext import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.asStateFlow -import kotlinx.coroutines.flow.launchIn -import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.flow.* import kotlinx.coroutines.launch import no.nordicsemi.android.ble.ktx.suspend import no.nordicsemi.android.service.BleManagerResult @@ -29,8 +26,8 @@ class UARTRepository @Inject constructor( private val _data = MutableStateFlow>(ConnectingResult()) internal val data = _data.asStateFlow() - private val _isRunning = MutableStateFlow(false) - val isRunning = _isRunning.asStateFlow() + val isRunning = data.map { it.isRunning() } + val hasBeenDisconnected = data.map { it.hasBeenDisconnected() } fun launch(device: BluetoothDevice) { serviceManager.startService(UARTService::class.java, device) @@ -59,16 +56,13 @@ class UARTRepository @Inject constructor( .useAutoConnect(false) .retry(3, 100) .suspend() - _isRunning.value = true } catch (e: Exception) { e.printStackTrace() } } fun release() { - serviceManager.stopService(UARTService::class.java) manager?.disconnect()?.enqueue() manager = null - _isRunning.value = false } } diff --git a/profile_uart/src/main/java/no/nordicsemi/android/uart/repository/UARTManager.kt b/profile_uart/src/main/java/no/nordicsemi/android/uart/repository/UARTManager.kt index d86004e1..685612d7 100644 --- a/profile_uart/src/main/java/no/nordicsemi/android/uart/repository/UARTManager.kt +++ b/profile_uart/src/main/java/no/nordicsemi/android/uart/repository/UARTManager.kt @@ -41,7 +41,7 @@ import no.nordicsemi.android.utils.EMPTY import no.nordicsemi.android.utils.launchWithCatch import java.util.* -val UART_SERVICE_UUID = UUID.fromString("6E400001-B5A3-F393-E0A9-E50E24DCCA9E") +val UART_SERVICE_UUID: UUID = UUID.fromString("6E400001-B5A3-F393-E0A9-E50E24DCCA9E") private val UART_RX_CHARACTERISTIC_UUID = UUID.fromString("6E400002-B5A3-F393-E0A9-E50E24DCCA9E") private val UART_TX_CHARACTERISTIC_UUID = UUID.fromString("6E400003-B5A3-F393-E0A9-E50E24DCCA9E") @@ -116,7 +116,7 @@ internal class UARTManager( gatt.getService(BATTERY_SERVICE_UUID)?.run { batteryLevelCharacteristic = getCharacteristic(BATTERY_LEVEL_CHARACTERISTIC_UUID) } - return rxCharacteristic != null && txCharacteristic != null && batteryLevelCharacteristic != null && (writeRequest || writeCommand) + return rxCharacteristic != null && txCharacteristic != null && (writeRequest || writeCommand) } override fun onServicesInvalidated() { 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 ff8ccd4c..76bc617d 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 @@ -4,6 +4,8 @@ import android.bluetooth.BluetoothDevice import android.content.Intent import androidx.lifecycle.lifecycleScope import dagger.hilt.android.AndroidEntryPoint +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach import no.nordicsemi.android.service.DEVICE_DATA import no.nordicsemi.android.service.NotificationService import no.nordicsemi.android.uart.data.UARTRepository @@ -22,6 +24,10 @@ internal class UARTService : NotificationService() { repository.start(device, lifecycleScope) + repository.hasBeenDisconnected.onEach { + if (it) stopSelf() + }.launchIn(lifecycleScope) + return START_REDELIVER_INTENT } } diff --git a/profile_uart/src/main/java/no/nordicsemi/android/uart/view/UARTScreen.kt b/profile_uart/src/main/java/no/nordicsemi/android/uart/view/UARTScreen.kt index 1af31071..8690d05f 100644 --- a/profile_uart/src/main/java/no/nordicsemi/android/uart/view/UARTScreen.kt +++ b/profile_uart/src/main/java/no/nordicsemi/android/uart/view/UARTScreen.kt @@ -26,20 +26,17 @@ fun UARTScreen() { Column { val navigateUp = { viewModel.onEvent(NavigateUp) } - BackIconAppBar(stringResource(id = R.string.uart_title)) { - viewModel.onEvent(DisconnectEvent) - } + BackIconAppBar(stringResource(id = R.string.uart_title), navigateUp) Column(modifier = Modifier.verticalScroll(rememberScrollState())) { when (state.uartManagerState) { NoDeviceState -> NoDeviceView() is WorkingState -> when (state.uartManagerState.result) { - is ConnectingResult, - is ReadyResult - -> DeviceConnectingView { viewModel.onEvent(DisconnectEvent) } + is ConnectingResult -> DeviceConnectingView { viewModel.onEvent(DisconnectEvent) } is DisconnectedResult -> DeviceDisconnectedView(Reason.USER, navigateUp) is LinkLossResult -> DeviceDisconnectedView(Reason.LINK_LOSS, navigateUp) is MissingServiceResult -> DeviceDisconnectedView(Reason.MISSING_SERVICE, navigateUp) + is UnknownErrorResult -> DeviceDisconnectedView(Reason.UNKNOWN, navigateUp) is SuccessResult -> UARTContentView(state.uartManagerState.result.data, state.macros) { viewModel.onEvent(it) } } }.exhaustive diff --git a/profile_uart/src/main/java/no/nordicsemi/android/uart/viewmodel/UARTViewModel.kt b/profile_uart/src/main/java/no/nordicsemi/android/uart/viewmodel/UARTViewModel.kt index f04bcba6..101784c1 100644 --- a/profile_uart/src/main/java/no/nordicsemi/android/uart/viewmodel/UARTViewModel.kt +++ b/profile_uart/src/main/java/no/nordicsemi/android/uart/viewmodel/UARTViewModel.kt @@ -3,10 +3,8 @@ package no.nordicsemi.android.uart.viewmodel import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import dagger.hilt.android.lifecycle.HiltViewModel -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.asStateFlow -import kotlinx.coroutines.flow.launchIn -import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.flow.* +import kotlinx.coroutines.launch import no.nordicsemi.android.navigation.* import no.nordicsemi.android.uart.data.UARTMacro import no.nordicsemi.android.uart.data.UARTRepository @@ -27,8 +25,10 @@ internal class UARTViewModel @Inject constructor( val state = _state.asStateFlow() init { - if (!repository.isRunning.value) { - requestBluetoothDevice() + viewModelScope.launch { + if (repository.isRunning.firstOrNull() == false) { + requestBluetoothDevice() + } } repository.data.onEach {