From 21679d546308127f54e436f509d4e02eb9894c11 Mon Sep 17 00:00:00 2001 From: hiar Date: Tue, 9 Sep 2025 13:19:39 +0200 Subject: [PATCH 01/11] TMP: DFU service --- .../android/toolbox/lib/utils/Profile.kt | 2 + .../android/toolbox/lib/utils/spec/Spec.kt | 3 ++ .../android/toolbox/profile/ProfileScreen.kt | 54 +++++++++++++++++++ .../toolbox/profile/manager/DFUManager.kt | 19 +++++++ 4 files changed, 78 insertions(+) create mode 100644 profile_manager/src/main/java/no/nordicsemi/android/toolbox/profile/manager/DFUManager.kt diff --git a/lib_utils/src/main/java/no/nordicsemi/android/toolbox/lib/utils/Profile.kt b/lib_utils/src/main/java/no/nordicsemi/android/toolbox/lib/utils/Profile.kt index 6de75e910..c45eb3e63 100644 --- a/lib_utils/src/main/java/no/nordicsemi/android/toolbox/lib/utils/Profile.kt +++ b/lib_utils/src/main/java/no/nordicsemi/android/toolbox/lib/utils/Profile.kt @@ -6,6 +6,7 @@ enum class Profile { CHANNEL_SOUNDING, CSC, DFS, + DFU, GLS, HRS, HTS, @@ -32,6 +33,7 @@ enum class Profile { BATTERY -> "Battery Service" THROUGHPUT -> "Throughput Service" UART -> "UART Service" + DFU -> "Device Firmware Update" } } \ No newline at end of file diff --git a/lib_utils/src/main/java/no/nordicsemi/android/toolbox/lib/utils/spec/Spec.kt b/lib_utils/src/main/java/no/nordicsemi/android/toolbox/lib/utils/spec/Spec.kt index 704b6de4f..e49d5dc8c 100644 --- a/lib_utils/src/main/java/no/nordicsemi/android/toolbox/lib/utils/spec/Spec.kt +++ b/lib_utils/src/main/java/no/nordicsemi/android/toolbox/lib/utils/spec/Spec.kt @@ -16,3 +16,6 @@ val BATTERY_SERVICE_UUID: UUID = UUID.fromString("0000180F-0000-1000-8000-00805f val THROUGHPUT_SERVICE_UUID: UUID = UUID.fromString("0483DADD-6C9D-6CA9-5D41-03AD4FFF4ABB") val CHANNEL_SOUND_SERVICE_UUID: UUID = UUID.fromString("0000185B-0000-1000-8000-00805F9B34FB") val LBS_SERVICE_UUID: UUID = UUID.fromString("00001523-1212-EFDE-1523-785FEABCD123") +val DFU_SERVICE_UUID: UUID = UUID.fromString("0000FE59-0000-1000-8000-00805F9B34FB") +val SMP_SERVICE_UUID: UUID = UUID.fromString("00001848-0000-1000-8000-00805F9B34FB") +val MDS_SERVICE_UUID: UUID = UUID.fromString("0000FFF6-0000-1000-8000-00805F9B34FB") diff --git a/profile/src/main/java/no/nordicsemi/android/toolbox/profile/ProfileScreen.kt b/profile/src/main/java/no/nordicsemi/android/toolbox/profile/ProfileScreen.kt index b4b685688..251b4eddc 100644 --- a/profile/src/main/java/no/nordicsemi/android/toolbox/profile/ProfileScreen.kt +++ b/profile/src/main/java/no/nordicsemi/android/toolbox/profile/ProfileScreen.kt @@ -7,12 +7,20 @@ import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.WindowInsetsSides import androidx.compose.foundation.layout.displayCutout import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.imePadding import androidx.compose.foundation.layout.only import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.widthIn import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.DeveloperBoardOff import androidx.compose.material3.Button +import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.OutlinedCard import androidx.compose.material3.Scaffold import androidx.compose.material3.Text import androidx.compose.runtime.Composable @@ -21,6 +29,7 @@ import androidx.compose.runtime.getValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import androidx.hilt.navigation.compose.hiltViewModel import androidx.lifecycle.compose.collectAsStateWithLifecycle @@ -204,9 +213,54 @@ internal fun DeviceConnectedView( Profile.BATTERY -> BatteryScreen() Profile.THROUGHPUT -> ThroughputScreen(state.maxValueLength) Profile.UART -> UARTScreen(state.maxValueLength) + Profile.DFU -> DFUScreen(modifier = Modifier.padding(16.dp)) } } } } } +} + +@Composable +internal fun DFUScreen( + modifier: Modifier = Modifier +) { + Column( + modifier = Modifier + .fillMaxSize() + .then(modifier), + horizontalAlignment = Alignment.CenterHorizontally + ) { + OutlinedCard( + modifier = Modifier + .widthIn(max = 460.dp), + ) { + Column( + modifier = Modifier + .fillMaxWidth() + .padding(16.dp), + verticalArrangement = Arrangement.spacedBy(16.dp), + horizontalAlignment = Alignment.CenterHorizontally, + ) { + Icon( + imageVector = Icons.Default.DeveloperBoardOff, + contentDescription = null, + tint = MaterialTheme.colorScheme.secondary, + modifier = Modifier.size(48.dp) + ) + + Text( + text = "DFU is not supported", + style = MaterialTheme.typography.titleMedium + ) + + Text( + text = "DFU is not implemented in the current version of the app." + + " Please use the Nordic Semiconductor app for firmware updates.", + textAlign = TextAlign.Center, + style = MaterialTheme.typography.bodyMedium + ) + } + } + } } \ No newline at end of file diff --git a/profile_manager/src/main/java/no/nordicsemi/android/toolbox/profile/manager/DFUManager.kt b/profile_manager/src/main/java/no/nordicsemi/android/toolbox/profile/manager/DFUManager.kt new file mode 100644 index 000000000..b21bbae36 --- /dev/null +++ b/profile_manager/src/main/java/no/nordicsemi/android/toolbox/profile/manager/DFUManager.kt @@ -0,0 +1,19 @@ +package no.nordicsemi.android.toolbox.profile.manager + +import kotlinx.coroutines.CoroutineScope +import no.nordicsemi.android.toolbox.lib.utils.Profile +import no.nordicsemi.kotlin.ble.client.RemoteService + +internal class DFUManager :ServiceManager{ + override val profile: Profile + get() = Profile.DFU + + override suspend fun observeServiceInteractions( + deviceId: String, + remoteService: RemoteService, + scope: CoroutineScope + ) { + TODO("Not yet implemented") + } + +} \ No newline at end of file From 1b50a49e9df9ce558f20e0b2ca340bbc93ef064d Mon Sep 17 00:00:00 2001 From: hiar Date: Tue, 9 Sep 2025 14:24:19 +0200 Subject: [PATCH 02/11] DFU uuids --- .../nordicsemi/android/toolbox/lib/utils/spec/Spec.kt | 6 ++++-- .../android/toolbox/profile/manager/DFUManager.kt | 2 +- .../toolbox/profile/manager/ServiceManagerFactory.kt | 11 +++++++++++ 3 files changed, 16 insertions(+), 3 deletions(-) diff --git a/lib_utils/src/main/java/no/nordicsemi/android/toolbox/lib/utils/spec/Spec.kt b/lib_utils/src/main/java/no/nordicsemi/android/toolbox/lib/utils/spec/Spec.kt index e49d5dc8c..4fea22ea1 100644 --- a/lib_utils/src/main/java/no/nordicsemi/android/toolbox/lib/utils/spec/Spec.kt +++ b/lib_utils/src/main/java/no/nordicsemi/android/toolbox/lib/utils/spec/Spec.kt @@ -17,5 +17,7 @@ val THROUGHPUT_SERVICE_UUID: UUID = UUID.fromString("0483DADD-6C9D-6CA9-5D41-03A val CHANNEL_SOUND_SERVICE_UUID: UUID = UUID.fromString("0000185B-0000-1000-8000-00805F9B34FB") val LBS_SERVICE_UUID: UUID = UUID.fromString("00001523-1212-EFDE-1523-785FEABCD123") val DFU_SERVICE_UUID: UUID = UUID.fromString("0000FE59-0000-1000-8000-00805F9B34FB") -val SMP_SERVICE_UUID: UUID = UUID.fromString("00001848-0000-1000-8000-00805F9B34FB") -val MDS_SERVICE_UUID: UUID = UUID.fromString("0000FFF6-0000-1000-8000-00805F9B34FB") +val LEGACY_DFU_SERVICE_UUID: UUID = UUID.fromString("00001530-1212-EFDE-1523-785FEABCD123") +val EXPERIMENTAL_BUTTONLESS_DFU_SERVICE_UUID: UUID = UUID.fromString("E2A00001-EC31-4EC3-A97A-1C34D87E9878") +val SMP_SERVICE_UUID: UUID = UUID.fromString("8D53DC1D-1DB7-4CD3-868B-8A527460AA84") +val MDS_SERVICE_UUID: UUID = UUID.fromString("54220000-F6A5-4007-A371-722F4EBD8436") diff --git a/profile_manager/src/main/java/no/nordicsemi/android/toolbox/profile/manager/DFUManager.kt b/profile_manager/src/main/java/no/nordicsemi/android/toolbox/profile/manager/DFUManager.kt index b21bbae36..0cdb283e7 100644 --- a/profile_manager/src/main/java/no/nordicsemi/android/toolbox/profile/manager/DFUManager.kt +++ b/profile_manager/src/main/java/no/nordicsemi/android/toolbox/profile/manager/DFUManager.kt @@ -13,7 +13,7 @@ internal class DFUManager :ServiceManager{ remoteService: RemoteService, scope: CoroutineScope ) { - TODO("Not yet implemented") + // No interactions to observe for DFU profile } } \ No newline at end of file diff --git a/profile_manager/src/main/java/no/nordicsemi/android/toolbox/profile/manager/ServiceManagerFactory.kt b/profile_manager/src/main/java/no/nordicsemi/android/toolbox/profile/manager/ServiceManagerFactory.kt index 77688cb0d..975b3a367 100644 --- a/profile_manager/src/main/java/no/nordicsemi/android/toolbox/profile/manager/ServiceManagerFactory.kt +++ b/profile_manager/src/main/java/no/nordicsemi/android/toolbox/profile/manager/ServiceManagerFactory.kt @@ -5,12 +5,17 @@ import no.nordicsemi.android.toolbox.lib.utils.spec.BPS_SERVICE_UUID import no.nordicsemi.android.toolbox.lib.utils.spec.CGMS_SERVICE_UUID import no.nordicsemi.android.toolbox.lib.utils.spec.CHANNEL_SOUND_SERVICE_UUID import no.nordicsemi.android.toolbox.lib.utils.spec.CSC_SERVICE_UUID +import no.nordicsemi.android.toolbox.lib.utils.spec.DFU_SERVICE_UUID import no.nordicsemi.android.toolbox.lib.utils.spec.DF_SERVICE_UUID +import no.nordicsemi.android.toolbox.lib.utils.spec.EXPERIMENTAL_BUTTONLESS_DFU_SERVICE_UUID import no.nordicsemi.android.toolbox.lib.utils.spec.GLS_SERVICE_UUID import no.nordicsemi.android.toolbox.lib.utils.spec.HRS_SERVICE_UUID import no.nordicsemi.android.toolbox.lib.utils.spec.HTS_SERVICE_UUID import no.nordicsemi.android.toolbox.lib.utils.spec.LBS_SERVICE_UUID +import no.nordicsemi.android.toolbox.lib.utils.spec.LEGACY_DFU_SERVICE_UUID +import no.nordicsemi.android.toolbox.lib.utils.spec.MDS_SERVICE_UUID import no.nordicsemi.android.toolbox.lib.utils.spec.RSCS_SERVICE_UUID +import no.nordicsemi.android.toolbox.lib.utils.spec.SMP_SERVICE_UUID import no.nordicsemi.android.toolbox.lib.utils.spec.THROUGHPUT_SERVICE_UUID import no.nordicsemi.android.toolbox.lib.utils.spec.UART_SERVICE_UUID import kotlin.uuid.ExperimentalUuidApi @@ -34,6 +39,12 @@ object ServiceManagerFactory { UART_SERVICE_UUID to ::UARTManager, CHANNEL_SOUND_SERVICE_UUID to ::ChannelSoundingManager, LBS_SERVICE_UUID to ::LBSManager, + DFU_SERVICE_UUID to ::DFUManager, + SMP_SERVICE_UUID to ::DFUManager, + MDS_SERVICE_UUID to ::DFUManager, + LEGACY_DFU_SERVICE_UUID to ::DFUManager, + EXPERIMENTAL_BUTTONLESS_DFU_SERVICE_UUID to ::DFUManager, + // Add more service UUIDs to handler mappings as needed ).mapKeys { it.key.toKotlinUuid() } From 606d040a4f84d99ecab70ad89f7f409a033f80b4 Mon Sep 17 00:00:00 2001 From: himalia416 Date: Tue, 30 Sep 2025 18:03:54 +0200 Subject: [PATCH 03/11] Added DFU service --- .../toolbox/profile/data/DFUServiceData.kt | 8 +++++++ .../toolbox/profile/manager/DFUManager.kt | 17 +++++++++++++- .../manager/repository/DFURepository.kt | 23 +++++++++++++++++++ 3 files changed, 47 insertions(+), 1 deletion(-) create mode 100644 profile_data/src/main/java/no/nordicsemi/android/toolbox/profile/data/DFUServiceData.kt create mode 100644 profile_manager/src/main/java/no/nordicsemi/android/toolbox/profile/manager/repository/DFURepository.kt diff --git a/profile_data/src/main/java/no/nordicsemi/android/toolbox/profile/data/DFUServiceData.kt b/profile_data/src/main/java/no/nordicsemi/android/toolbox/profile/data/DFUServiceData.kt new file mode 100644 index 000000000..ebc1512d1 --- /dev/null +++ b/profile_data/src/main/java/no/nordicsemi/android/toolbox/profile/data/DFUServiceData.kt @@ -0,0 +1,8 @@ +package no.nordicsemi.android.toolbox.profile.data + +import no.nordicsemi.android.toolbox.lib.utils.Profile + +data class DFUServiceData( + override val profile: Profile = Profile.DFU, + val dfuAppName : String? = null, +): ProfileServiceData() \ No newline at end of file diff --git a/profile_manager/src/main/java/no/nordicsemi/android/toolbox/profile/manager/DFUManager.kt b/profile_manager/src/main/java/no/nordicsemi/android/toolbox/profile/manager/DFUManager.kt index 0cdb283e7..fa972f5ed 100644 --- a/profile_manager/src/main/java/no/nordicsemi/android/toolbox/profile/manager/DFUManager.kt +++ b/profile_manager/src/main/java/no/nordicsemi/android/toolbox/profile/manager/DFUManager.kt @@ -2,18 +2,33 @@ package no.nordicsemi.android.toolbox.profile.manager import kotlinx.coroutines.CoroutineScope import no.nordicsemi.android.toolbox.lib.utils.Profile +import no.nordicsemi.android.toolbox.lib.utils.spec.DFU_SERVICE_UUID +import no.nordicsemi.android.toolbox.lib.utils.spec.EXPERIMENTAL_BUTTONLESS_DFU_SERVICE_UUID +import no.nordicsemi.android.toolbox.lib.utils.spec.LEGACY_DFU_SERVICE_UUID +import no.nordicsemi.android.toolbox.lib.utils.spec.SMP_SERVICE_UUID import no.nordicsemi.kotlin.ble.client.RemoteService +import kotlin.uuid.ExperimentalUuidApi +import kotlin.uuid.toKotlinUuid internal class DFUManager :ServiceManager{ override val profile: Profile get() = Profile.DFU + @OptIn(ExperimentalUuidApi::class) override suspend fun observeServiceInteractions( deviceId: String, remoteService: RemoteService, scope: CoroutineScope ) { - // No interactions to observe for DFU profile + when (remoteService.uuid) { + DFU_SERVICE_UUID.toKotlinUuid(), + SMP_SERVICE_UUID.toKotlinUuid(), + LEGACY_DFU_SERVICE_UUID.toKotlinUuid(), + EXPERIMENTAL_BUTTONLESS_DFU_SERVICE_UUID.toKotlinUuid() -> this + + else -> null + + } } } \ No newline at end of file diff --git a/profile_manager/src/main/java/no/nordicsemi/android/toolbox/profile/manager/repository/DFURepository.kt b/profile_manager/src/main/java/no/nordicsemi/android/toolbox/profile/manager/repository/DFURepository.kt new file mode 100644 index 000000000..36057f677 --- /dev/null +++ b/profile_manager/src/main/java/no/nordicsemi/android/toolbox/profile/manager/repository/DFURepository.kt @@ -0,0 +1,23 @@ +package no.nordicsemi.android.toolbox.profile.manager.repository + +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.update +import no.nordicsemi.android.toolbox.profile.data.DFUServiceData + +object DFURepository { + private val _dataMap = mutableMapOf>() + + fun getData(deviceId: String): Flow { + return _dataMap.getOrPut(deviceId) { MutableStateFlow(DFUServiceData()) } + } + + fun updateAppName(deviceId: String, appName: String) { + _dataMap[deviceId]?.update { it.copy(dfuAppName = appName) } + } + + fun clear(deviceId: String) { + _dataMap.remove(deviceId) + } + +} \ No newline at end of file From 5c3b86afe6eac46767655190c656bdbe21cc491b Mon Sep 17 00:00:00 2001 From: hiar Date: Wed, 1 Oct 2025 08:59:16 +0200 Subject: [PATCH 04/11] DFU screen --- .../android/toolbox/profile/view/DFUScreen.kt | 76 +++++++++++++++++++ .../src/main/res/drawable/ic_dfu.xml | 0 2 files changed, 76 insertions(+) create mode 100644 profile/src/main/java/no/nordicsemi/android/toolbox/profile/view/DFUScreen.kt rename {lib_ui => profile}/src/main/res/drawable/ic_dfu.xml (100%) diff --git a/profile/src/main/java/no/nordicsemi/android/toolbox/profile/view/DFUScreen.kt b/profile/src/main/java/no/nordicsemi/android/toolbox/profile/view/DFUScreen.kt new file mode 100644 index 000000000..e3ed6b8af --- /dev/null +++ b/profile/src/main/java/no/nordicsemi/android/toolbox/profile/view/DFUScreen.kt @@ -0,0 +1,76 @@ +package no.nordicsemi.android.toolbox.profile.view + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.widthIn +import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.OutlinedCard +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import no.nordicsemi.android.common.theme.NordicTheme +import no.nordicsemi.android.toolbox.profile.R + +@Composable +internal fun DFUScreen( + modifier: Modifier = Modifier +) { + Column( + modifier = Modifier + .fillMaxSize() + .then(modifier), + horizontalAlignment = Alignment.CenterHorizontally + ) { + OutlinedCard( + modifier = Modifier + .widthIn(max = 460.dp), + ) { + Column( + modifier = Modifier + .fillMaxWidth() + .padding(16.dp), + verticalArrangement = Arrangement.spacedBy(16.dp), + horizontalAlignment = Alignment.CenterHorizontally, + ) { + Icon( + painter = painterResource(R.drawable.ic_dfu), + contentDescription = null, + tint = MaterialTheme.colorScheme.onSurfaceVariant, + modifier = Modifier.size(48.dp) + ) + + Text( + text = "DFU is not supported", + style = MaterialTheme.typography.titleMedium + ) + + Text( + text = "DFU service is not not available in the current version of the app." + + "Please use the DFU app from Nordic Semiconductor to update your device’s firmware.", + textAlign = TextAlign.Center, + style = MaterialTheme.typography.bodyMedium + ) + } + } + } +} + +@Preview(showBackground = true) +@Composable +private fun DeviceDisconnectedViewPreview() { + NordicTheme { + DFUScreen( + modifier = Modifier.padding(16.dp) + ) + } +} diff --git a/lib_ui/src/main/res/drawable/ic_dfu.xml b/profile/src/main/res/drawable/ic_dfu.xml similarity index 100% rename from lib_ui/src/main/res/drawable/ic_dfu.xml rename to profile/src/main/res/drawable/ic_dfu.xml From 34f8b0689dc97c2220c2b8c441e6eb5458e455b8 Mon Sep 17 00:00:00 2001 From: hiar Date: Wed, 1 Oct 2025 09:10:15 +0200 Subject: [PATCH 05/11] Remove duplicate --- .../android/toolbox/profile/ProfileScreen.kt | 59 ++----------------- 1 file changed, 5 insertions(+), 54 deletions(-) diff --git a/profile/src/main/java/no/nordicsemi/android/toolbox/profile/ProfileScreen.kt b/profile/src/main/java/no/nordicsemi/android/toolbox/profile/ProfileScreen.kt index 251b4eddc..e95c47303 100644 --- a/profile/src/main/java/no/nordicsemi/android/toolbox/profile/ProfileScreen.kt +++ b/profile/src/main/java/no/nordicsemi/android/toolbox/profile/ProfileScreen.kt @@ -7,20 +7,12 @@ import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.WindowInsetsSides import androidx.compose.foundation.layout.displayCutout import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.imePadding import androidx.compose.foundation.layout.only import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size -import androidx.compose.foundation.layout.widthIn import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.DeveloperBoardOff import androidx.compose.material3.Button -import androidx.compose.material3.Icon -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.OutlinedCard import androidx.compose.material3.Scaffold import androidx.compose.material3.Text import androidx.compose.runtime.Composable @@ -29,7 +21,6 @@ import androidx.compose.runtime.getValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource -import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import androidx.hilt.navigation.compose.hiltViewModel import androidx.lifecycle.compose.collectAsStateWithLifecycle @@ -38,6 +29,7 @@ import no.nordicsemi.android.common.permissions.ble.RequireLocation import no.nordicsemi.android.common.permissions.notification.RequestNotificationPermission import no.nordicsemi.android.toolbox.lib.utils.Profile import no.nordicsemi.android.toolbox.profile.data.displayMessage +import no.nordicsemi.android.toolbox.profile.view.DFUScreen import no.nordicsemi.android.toolbox.profile.view.battery.BatteryScreen import no.nordicsemi.android.toolbox.profile.view.bps.BPSScreen import no.nordicsemi.android.toolbox.profile.view.cgms.CGMScreen @@ -201,7 +193,10 @@ internal fun DeviceConnectedView( // Display the appropriate screen for each profile. when (serviceManager.profile) { Profile.HTS -> HTSScreen() - Profile.CHANNEL_SOUNDING -> ChannelSoundingScreen(isNotificationPermissionGranted) + Profile.CHANNEL_SOUNDING -> ChannelSoundingScreen( + isNotificationPermissionGranted + ) + Profile.BPS -> BPSScreen() Profile.CSC -> CSCScreen() Profile.CGM -> CGMScreen() @@ -220,47 +215,3 @@ internal fun DeviceConnectedView( } } } - -@Composable -internal fun DFUScreen( - modifier: Modifier = Modifier -) { - Column( - modifier = Modifier - .fillMaxSize() - .then(modifier), - horizontalAlignment = Alignment.CenterHorizontally - ) { - OutlinedCard( - modifier = Modifier - .widthIn(max = 460.dp), - ) { - Column( - modifier = Modifier - .fillMaxWidth() - .padding(16.dp), - verticalArrangement = Arrangement.spacedBy(16.dp), - horizontalAlignment = Alignment.CenterHorizontally, - ) { - Icon( - imageVector = Icons.Default.DeveloperBoardOff, - contentDescription = null, - tint = MaterialTheme.colorScheme.secondary, - modifier = Modifier.size(48.dp) - ) - - Text( - text = "DFU is not supported", - style = MaterialTheme.typography.titleMedium - ) - - Text( - text = "DFU is not implemented in the current version of the app." + - " Please use the Nordic Semiconductor app for firmware updates.", - textAlign = TextAlign.Center, - style = MaterialTheme.typography.bodyMedium - ) - } - } - } -} \ No newline at end of file From 560d0e03c1f1b622a86c47d16e50ddc99d052409 Mon Sep 17 00:00:00 2001 From: hiar Date: Wed, 1 Oct 2025 16:36:26 +0200 Subject: [PATCH 06/11] dfu redirection --- .../android/toolbox/profile/ProfileScreen.kt | 2 +- .../profile/view/{ => dfu}/DFUScreen.kt | 46 +++++++++++++++++-- .../toolbox/profile/view/dfu/DfuUiMapper.kt | 32 +++++++++++++ .../toolbox/profile/data/DFUServiceData.kt | 12 ++++- .../toolbox/profile/manager/DFUManager.kt | 19 +++++--- .../manager/repository/DFURepository.kt | 3 +- 6 files changed, 100 insertions(+), 14 deletions(-) rename profile/src/main/java/no/nordicsemi/android/toolbox/profile/view/{ => dfu}/DFUScreen.kt (59%) create mode 100644 profile/src/main/java/no/nordicsemi/android/toolbox/profile/view/dfu/DfuUiMapper.kt diff --git a/profile/src/main/java/no/nordicsemi/android/toolbox/profile/ProfileScreen.kt b/profile/src/main/java/no/nordicsemi/android/toolbox/profile/ProfileScreen.kt index e95c47303..62370305f 100644 --- a/profile/src/main/java/no/nordicsemi/android/toolbox/profile/ProfileScreen.kt +++ b/profile/src/main/java/no/nordicsemi/android/toolbox/profile/ProfileScreen.kt @@ -29,7 +29,7 @@ import no.nordicsemi.android.common.permissions.ble.RequireLocation import no.nordicsemi.android.common.permissions.notification.RequestNotificationPermission import no.nordicsemi.android.toolbox.lib.utils.Profile import no.nordicsemi.android.toolbox.profile.data.displayMessage -import no.nordicsemi.android.toolbox.profile.view.DFUScreen +import no.nordicsemi.android.toolbox.profile.view.dfu.DFUScreen import no.nordicsemi.android.toolbox.profile.view.battery.BatteryScreen import no.nordicsemi.android.toolbox.profile.view.bps.BPSScreen import no.nordicsemi.android.toolbox.profile.view.cgms.CGMScreen diff --git a/profile/src/main/java/no/nordicsemi/android/toolbox/profile/view/DFUScreen.kt b/profile/src/main/java/no/nordicsemi/android/toolbox/profile/view/dfu/DFUScreen.kt similarity index 59% rename from profile/src/main/java/no/nordicsemi/android/toolbox/profile/view/DFUScreen.kt rename to profile/src/main/java/no/nordicsemi/android/toolbox/profile/view/dfu/DFUScreen.kt index e3ed6b8af..94d7682c8 100644 --- a/profile/src/main/java/no/nordicsemi/android/toolbox/profile/view/DFUScreen.kt +++ b/profile/src/main/java/no/nordicsemi/android/toolbox/profile/view/dfu/DFUScreen.kt @@ -1,12 +1,16 @@ -package no.nordicsemi.android.toolbox.profile.view +package no.nordicsemi.android.toolbox.profile.view.dfu import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.widthIn +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Download +import androidx.compose.material3.Button import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.OutlinedCard @@ -14,6 +18,8 @@ import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.platform.LocalUriHandler import androidx.compose.ui.res.painterResource import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.Preview @@ -29,7 +35,8 @@ internal fun DFUScreen( modifier = Modifier .fillMaxSize() .then(modifier), - horizontalAlignment = Alignment.CenterHorizontally + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.spacedBy(16.dp) ) { OutlinedCard( modifier = Modifier @@ -55,13 +62,46 @@ internal fun DFUScreen( ) Text( - text = "DFU service is not not available in the current version of the app." + + text = "DFU service is not available in the current version of the app. " + "Please use the DFU app from Nordic Semiconductor to update your device’s firmware.", textAlign = TextAlign.Center, style = MaterialTheme.typography.bodyMedium ) } } + + val uriHandler = LocalUriHandler.current + val context = LocalContext.current + val packageManger = context.packageManager + val intent = packageManger.getLaunchIntentForPackage(DFU_PACKAGE_NAME) + + val description = intent?.let { + "Open DFU" + } ?: "Download from Play Store" + + Button( + onClick = { + if (intent != null) { + context.startActivity(intent) + } else { + uriHandler.openUri(DFU_APP_LINK) + } + }, + ) { + Row { + intent?.let { + Icon( + painter = painterResource(R.drawable.ic_dfu), + contentDescription = null, + modifier = Modifier + .size(24.dp) + .padding(end = 8.dp) + ) + } ?: Icon(imageVector = Icons.Default.Download, contentDescription = null) + Text(text = description) + } + + } } } diff --git a/profile/src/main/java/no/nordicsemi/android/toolbox/profile/view/dfu/DfuUiMapper.kt b/profile/src/main/java/no/nordicsemi/android/toolbox/profile/view/dfu/DfuUiMapper.kt new file mode 100644 index 000000000..d57f35793 --- /dev/null +++ b/profile/src/main/java/no/nordicsemi/android/toolbox/profile/view/dfu/DfuUiMapper.kt @@ -0,0 +1,32 @@ +package no.nordicsemi.android.toolbox.profile.view.dfu + +import no.nordicsemi.android.toolbox.profile.data.DFUsAvailable + + +internal const val DFU_PACKAGE_NAME = "no.nordicsemi.android.dfu" +internal const val DFU_APP_LINK = + "https://play.google.com/store/apps/details?id=no.nordicsemi.android.dfu" + +internal const val SMP_PACKAGE_NAME = "no.nordicsemi.android.nrfconnectdevicemanager" +internal const val SMP_APP_LINK = + "https://play.google.com/store/apps/details?id=no.nordicsemi.android.nrfconnectdevicemanager" + +internal fun DFUsAvailable.getApp(): String { + return when (this) { + DFUsAvailable.DFU_SERVICE -> DFU_APP_LINK + DFUsAvailable.SMP_SERVICE -> SMP_APP_LINK + DFUsAvailable.MDS_SERVICE -> SMP_APP_LINK + DFUsAvailable.LEGACY_DFU_SERVICE -> DFU_APP_LINK + DFUsAvailable.EXPERIMENTAL_BUTTONLESS_DFU_SERVICE -> DFU_APP_LINK + } +} + +internal fun DFUsAvailable.getPackageName(): String { + return when (this) { + DFUsAvailable.DFU_SERVICE -> DFU_PACKAGE_NAME + DFUsAvailable.SMP_SERVICE -> SMP_PACKAGE_NAME + DFUsAvailable.MDS_SERVICE -> SMP_PACKAGE_NAME + DFUsAvailable.LEGACY_DFU_SERVICE -> DFU_PACKAGE_NAME + DFUsAvailable.EXPERIMENTAL_BUTTONLESS_DFU_SERVICE -> DFU_PACKAGE_NAME + } +} diff --git a/profile_data/src/main/java/no/nordicsemi/android/toolbox/profile/data/DFUServiceData.kt b/profile_data/src/main/java/no/nordicsemi/android/toolbox/profile/data/DFUServiceData.kt index ebc1512d1..45698a2aa 100644 --- a/profile_data/src/main/java/no/nordicsemi/android/toolbox/profile/data/DFUServiceData.kt +++ b/profile_data/src/main/java/no/nordicsemi/android/toolbox/profile/data/DFUServiceData.kt @@ -4,5 +4,13 @@ import no.nordicsemi.android.toolbox.lib.utils.Profile data class DFUServiceData( override val profile: Profile = Profile.DFU, - val dfuAppName : String? = null, -): ProfileServiceData() \ No newline at end of file + val dfuAppName : DFUsAvailable? = null, +): ProfileServiceData() + +enum class DFUsAvailable { + DFU_SERVICE, + SMP_SERVICE, + MDS_SERVICE, + LEGACY_DFU_SERVICE, + EXPERIMENTAL_BUTTONLESS_DFU_SERVICE; +} \ No newline at end of file diff --git a/profile_manager/src/main/java/no/nordicsemi/android/toolbox/profile/manager/DFUManager.kt b/profile_manager/src/main/java/no/nordicsemi/android/toolbox/profile/manager/DFUManager.kt index fa972f5ed..837ae970a 100644 --- a/profile_manager/src/main/java/no/nordicsemi/android/toolbox/profile/manager/DFUManager.kt +++ b/profile_manager/src/main/java/no/nordicsemi/android/toolbox/profile/manager/DFUManager.kt @@ -5,12 +5,15 @@ import no.nordicsemi.android.toolbox.lib.utils.Profile import no.nordicsemi.android.toolbox.lib.utils.spec.DFU_SERVICE_UUID import no.nordicsemi.android.toolbox.lib.utils.spec.EXPERIMENTAL_BUTTONLESS_DFU_SERVICE_UUID import no.nordicsemi.android.toolbox.lib.utils.spec.LEGACY_DFU_SERVICE_UUID +import no.nordicsemi.android.toolbox.lib.utils.spec.MDS_SERVICE_UUID import no.nordicsemi.android.toolbox.lib.utils.spec.SMP_SERVICE_UUID +import no.nordicsemi.android.toolbox.profile.data.DFUsAvailable +import no.nordicsemi.android.toolbox.profile.manager.repository.DFURepository import no.nordicsemi.kotlin.ble.client.RemoteService import kotlin.uuid.ExperimentalUuidApi import kotlin.uuid.toKotlinUuid -internal class DFUManager :ServiceManager{ +internal class DFUManager : ServiceManager { override val profile: Profile get() = Profile.DFU @@ -20,15 +23,17 @@ internal class DFUManager :ServiceManager{ remoteService: RemoteService, scope: CoroutineScope ) { - when (remoteService.uuid) { - DFU_SERVICE_UUID.toKotlinUuid(), - SMP_SERVICE_UUID.toKotlinUuid(), - LEGACY_DFU_SERVICE_UUID.toKotlinUuid(), - EXPERIMENTAL_BUTTONLESS_DFU_SERVICE_UUID.toKotlinUuid() -> this + val appName = when (remoteService.uuid) { + DFU_SERVICE_UUID.toKotlinUuid() -> DFUsAvailable.DFU_SERVICE + SMP_SERVICE_UUID.toKotlinUuid() -> DFUsAvailable.SMP_SERVICE + LEGACY_DFU_SERVICE_UUID.toKotlinUuid() -> DFUsAvailable.LEGACY_DFU_SERVICE + EXPERIMENTAL_BUTTONLESS_DFU_SERVICE_UUID.toKotlinUuid() -> DFUsAvailable.EXPERIMENTAL_BUTTONLESS_DFU_SERVICE + MDS_SERVICE_UUID.toKotlinUuid() -> DFUsAvailable.MDS_SERVICE else -> null - } + if (appName != null) + DFURepository.updateAppName(deviceId, appName) } } \ No newline at end of file diff --git a/profile_manager/src/main/java/no/nordicsemi/android/toolbox/profile/manager/repository/DFURepository.kt b/profile_manager/src/main/java/no/nordicsemi/android/toolbox/profile/manager/repository/DFURepository.kt index 36057f677..8bbea5874 100644 --- a/profile_manager/src/main/java/no/nordicsemi/android/toolbox/profile/manager/repository/DFURepository.kt +++ b/profile_manager/src/main/java/no/nordicsemi/android/toolbox/profile/manager/repository/DFURepository.kt @@ -4,6 +4,7 @@ import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.update import no.nordicsemi.android.toolbox.profile.data.DFUServiceData +import no.nordicsemi.android.toolbox.profile.data.DFUsAvailable object DFURepository { private val _dataMap = mutableMapOf>() @@ -12,7 +13,7 @@ object DFURepository { return _dataMap.getOrPut(deviceId) { MutableStateFlow(DFUServiceData()) } } - fun updateAppName(deviceId: String, appName: String) { + fun updateAppName(deviceId: String, appName: DFUsAvailable) { _dataMap[deviceId]?.update { it.copy(dfuAppName = appName) } } From 068cc623eb9b85bd804dd64e3b687eac782c7d1f Mon Sep 17 00:00:00 2001 From: himalia416 Date: Mon, 6 Oct 2025 12:41:42 +0200 Subject: [PATCH 07/11] Removed unused directory --- permissions-ranging/build.gradle.kts | 11 --- permissions-ranging/module-rules.pro | 21 ----- .../src/main/AndroidManifest.xml | 6 -- .../RequestRangingPermission.kt | 42 --------- .../repository/RangingStateManager.kt | 89 ------------------- .../utils/LocalDataProvider.kt | 35 -------- .../utils/RangingPermissionState.kt | 23 ----- .../utils/RangingPermissionUtils.kt | 57 ------------ .../view/RangingPermissionRequestView.kt | 49 ---------- .../viewmodel/RangingPermissionViewModel.kt | 37 -------- 10 files changed, 370 deletions(-) delete mode 100644 permissions-ranging/build.gradle.kts delete mode 100644 permissions-ranging/module-rules.pro delete mode 100644 permissions-ranging/src/main/AndroidManifest.xml delete mode 100644 permissions-ranging/src/main/java/no/nordicsemi/android/permissions_ranging/RequestRangingPermission.kt delete mode 100644 permissions-ranging/src/main/java/no/nordicsemi/android/permissions_ranging/repository/RangingStateManager.kt delete mode 100644 permissions-ranging/src/main/java/no/nordicsemi/android/permissions_ranging/utils/LocalDataProvider.kt delete mode 100644 permissions-ranging/src/main/java/no/nordicsemi/android/permissions_ranging/utils/RangingPermissionState.kt delete mode 100644 permissions-ranging/src/main/java/no/nordicsemi/android/permissions_ranging/utils/RangingPermissionUtils.kt delete mode 100644 permissions-ranging/src/main/java/no/nordicsemi/android/permissions_ranging/view/RangingPermissionRequestView.kt delete mode 100644 permissions-ranging/src/main/java/no/nordicsemi/android/permissions_ranging/viewmodel/RangingPermissionViewModel.kt diff --git a/permissions-ranging/build.gradle.kts b/permissions-ranging/build.gradle.kts deleted file mode 100644 index 8d251185d..000000000 --- a/permissions-ranging/build.gradle.kts +++ /dev/null @@ -1,11 +0,0 @@ -plugins { - alias(libs.plugins.nordic.feature) -} - -android { - namespace = "no.nordicsemi.android.permissions_ranging" -} - -dependencies { - implementation(libs.accompanist.permissions) -} \ No newline at end of file diff --git a/permissions-ranging/module-rules.pro b/permissions-ranging/module-rules.pro deleted file mode 100644 index 481bb4348..000000000 --- a/permissions-ranging/module-rules.pro +++ /dev/null @@ -1,21 +0,0 @@ -# Add project specific ProGuard rules here. -# You can control the set of applied configuration files using the -# proguardFiles setting in build.gradle. -# -# For more details, see -# http://developer.android.com/guide/developing/tools/proguard.html - -# If your project uses WebView with JS, uncomment the following -# and specify the fully qualified class name to the JavaScript interface -# class: -#-keepclassmembers class fqcn.of.javascript.interface.for.webview { -# public *; -#} - -# Uncomment this to preserve the line number information for -# debugging stack traces. -#-keepattributes SourceFile,LineNumberTable - -# If you keep the line number information, uncomment this to -# hide the original source file name. -#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/permissions-ranging/src/main/AndroidManifest.xml b/permissions-ranging/src/main/AndroidManifest.xml deleted file mode 100644 index b406fd5be..000000000 --- a/permissions-ranging/src/main/AndroidManifest.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/permissions-ranging/src/main/java/no/nordicsemi/android/permissions_ranging/RequestRangingPermission.kt b/permissions-ranging/src/main/java/no/nordicsemi/android/permissions_ranging/RequestRangingPermission.kt deleted file mode 100644 index 3b0350dcf..000000000 --- a/permissions-ranging/src/main/java/no/nordicsemi/android/permissions_ranging/RequestRangingPermission.kt +++ /dev/null @@ -1,42 +0,0 @@ -package no.nordicsemi.android.permissions_ranging - -import android.app.Activity -import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.getValue -import androidx.compose.ui.platform.LocalContext -import androidx.hilt.navigation.compose.hiltViewModel -import androidx.lifecycle.compose.collectAsStateWithLifecycle -import no.nordicsemi.android.permissions_ranging.utils.RangingNotAvailableReason -import no.nordicsemi.android.permissions_ranging.utils.RangingPermissionState -import no.nordicsemi.android.permissions_ranging.view.RangingPermissionRequestView -import no.nordicsemi.android.permissions_ranging.viewmodel.RangingPermissionViewModel - -@Composable -fun RequestRangingPermission( - onChanged: (Boolean) -> Unit = {}, - content: @Composable (Boolean) -> Unit, -) { - val permissionViewModel = hiltViewModel() - val context = LocalContext.current - val activity = context as? Activity - - val state by activity?.let { permissionViewModel.requestRangingPermission(it) }!! - .collectAsStateWithLifecycle() - - - LaunchedEffect(state) { - onChanged(state is RangingPermissionState.Available) - } - - when (val s = state) { - is RangingPermissionState.Available -> content(true) - is RangingPermissionState.NotAvailable -> { - when (s.reason) { - RangingNotAvailableReason.NOT_AVAILABLE -> RangingPermissionRequestView(content) - RangingNotAvailableReason.PERMISSION_DENIED -> content(false) - } - } - } - -} \ No newline at end of file diff --git a/permissions-ranging/src/main/java/no/nordicsemi/android/permissions_ranging/repository/RangingStateManager.kt b/permissions-ranging/src/main/java/no/nordicsemi/android/permissions_ranging/repository/RangingStateManager.kt deleted file mode 100644 index 8380a2ee5..000000000 --- a/permissions-ranging/src/main/java/no/nordicsemi/android/permissions_ranging/repository/RangingStateManager.kt +++ /dev/null @@ -1,89 +0,0 @@ -package no.nordicsemi.android.permissions_ranging.repository - -import android.app.Activity -import android.content.BroadcastReceiver -import android.content.Context -import android.content.Intent -import android.content.IntentFilter -import androidx.core.app.ActivityCompat -import androidx.core.content.ContextCompat -import dagger.hilt.android.qualifiers.ApplicationContext -import kotlinx.coroutines.channels.awaitClose -import kotlinx.coroutines.flow.callbackFlow -import no.nordicsemi.android.permissions_ranging.utils.LocalDataProvider -import no.nordicsemi.android.permissions_ranging.utils.RangingNotAvailableReason -import no.nordicsemi.android.permissions_ranging.utils.RangingPermissionState -import no.nordicsemi.android.permissions_ranging.utils.RangingPermissionUtils -import javax.inject.Inject -import javax.inject.Singleton - -private const val REFRESH_PERMISSIONS = - "no.nordicsemi.android.permissions_ranging.repository.REFRESH_RANGING_PERMISSIONS" -private const val RANGING_PERMISSION_REQUEST_CODE = 1001 - -@Singleton -internal class RangingStateManager @Inject constructor( - @param:ApplicationContext private val context: Context, -) { - private val dataProvider = LocalDataProvider(context) - private val utils = RangingPermissionUtils(context, dataProvider) - - fun rangingPermissionState(activity: Activity) = callbackFlow { - trySend(getRangingPermissionState()) - - val rangingStateChangeHandler = object : BroadcastReceiver() { - override fun onReceive(context: Context?, intent: Intent?) { - trySend(getRangingPermissionState()) - } - } - ContextCompat.registerReceiver( - context, - rangingStateChangeHandler, - IntentFilter(), - ContextCompat.RECEIVER_EXPORTED - ) - - ActivityCompat.requestPermissions( - activity, - arrayOf("android.permission.RANGING"), - RANGING_PERMISSION_REQUEST_CODE - ) - - awaitClose { - - context.unregisterReceiver(rangingStateChangeHandler) - } - - } - - fun refreshRangingPermissionState() { - val intent = Intent(REFRESH_PERMISSIONS) - context.sendBroadcast(intent) - } - - fun markRangingPermissionAsRequested() { - dataProvider.isRangingPermissionRequested = true - } - - fun isRangingPermissionDenied(): Boolean { - return try { - utils.isRangingPermissionDenied() - } catch (_: Exception) { - false - } - } - - private fun getRangingPermissionState(): RangingPermissionState { - return when { - !utils.isRangingPermissionAvailable -> RangingPermissionState.NotAvailable( - RangingNotAvailableReason.NOT_AVAILABLE - ) - - utils.isRangingPermissionAvailable && !utils.isRangingPermissionGranted -> RangingPermissionState.NotAvailable( - RangingNotAvailableReason.PERMISSION_DENIED - ) - - else -> RangingPermissionState.Available - } - } -} \ No newline at end of file diff --git a/permissions-ranging/src/main/java/no/nordicsemi/android/permissions_ranging/utils/LocalDataProvider.kt b/permissions-ranging/src/main/java/no/nordicsemi/android/permissions_ranging/utils/LocalDataProvider.kt deleted file mode 100644 index 2420d8253..000000000 --- a/permissions-ranging/src/main/java/no/nordicsemi/android/permissions_ranging/utils/LocalDataProvider.kt +++ /dev/null @@ -1,35 +0,0 @@ -package no.nordicsemi.android.permissions_ranging.utils - -import android.content.Context -import android.content.SharedPreferences -import android.os.Build -import androidx.annotation.ChecksSdkIntAtLeast -import androidx.core.app.ActivityCompat -import androidx.core.content.edit -import javax.inject.Inject -import javax.inject.Singleton - -private const val SHARED_PREFS_NAME = "SHARED_PREFS_RANGING" -private const val PREFS_PERMISSION_REQUESTED = "ranging_permission_requested" - -@Singleton -internal class LocalDataProvider @Inject constructor( - private val context: Context, -) { - private val sharedPrefs: SharedPreferences - get() = context.getSharedPreferences(SHARED_PREFS_NAME, Context.MODE_PRIVATE) - - val isBaklavaOrAbove: Boolean - @ChecksSdkIntAtLeast(api = Build.VERSION_CODES.BAKLAVA) - get() = Build.VERSION.SDK_INT >= Build.VERSION_CODES.BAKLAVA - - /** - * The first time an app requests a permission there is no 'Don't Allow' checkbox and - * [ActivityCompat.shouldShowRequestPermissionRationale] returns false. - */ - var isRangingPermissionRequested: Boolean - get() = sharedPrefs.getBoolean(PREFS_PERMISSION_REQUESTED, false) - set(value) { - sharedPrefs.edit { putBoolean(PREFS_PERMISSION_REQUESTED, value) } - } -} \ No newline at end of file diff --git a/permissions-ranging/src/main/java/no/nordicsemi/android/permissions_ranging/utils/RangingPermissionState.kt b/permissions-ranging/src/main/java/no/nordicsemi/android/permissions_ranging/utils/RangingPermissionState.kt deleted file mode 100644 index f6521b95b..000000000 --- a/permissions-ranging/src/main/java/no/nordicsemi/android/permissions_ranging/utils/RangingPermissionState.kt +++ /dev/null @@ -1,23 +0,0 @@ -package no.nordicsemi.android.permissions_ranging.utils - -internal sealed class RangingPermissionState { - /** - * Ranging is available and the app has the required permissions. - */ - data object Available : RangingPermissionState() - - /** - * Ranging is not available. - */ - data class NotAvailable( - val reason: RangingNotAvailableReason, - ) : RangingPermissionState() -} - -internal enum class RangingNotAvailableReason { - /** Ranging is not available on this device. */ - NOT_AVAILABLE, - - /** The app does not have the required permissions. */ - PERMISSION_DENIED, -} \ No newline at end of file diff --git a/permissions-ranging/src/main/java/no/nordicsemi/android/permissions_ranging/utils/RangingPermissionUtils.kt b/permissions-ranging/src/main/java/no/nordicsemi/android/permissions_ranging/utils/RangingPermissionUtils.kt deleted file mode 100644 index 231e73513..000000000 --- a/permissions-ranging/src/main/java/no/nordicsemi/android/permissions_ranging/utils/RangingPermissionUtils.kt +++ /dev/null @@ -1,57 +0,0 @@ -package no.nordicsemi.android.permissions_ranging.utils - -import android.Manifest -import android.app.Activity -import android.content.Context -import android.content.ContextWrapper -import android.content.pm.PackageManager -import android.os.Build -import androidx.annotation.ChecksSdkIntAtLeast -import androidx.core.content.ContextCompat - -internal class RangingPermissionUtils( - private val context: Context, - private val dataProvider: LocalDataProvider, -) { - val isRangingPermissionAvailable: Boolean - @ChecksSdkIntAtLeast(api = Build.VERSION_CODES.BAKLAVA) - get() = Build.VERSION.SDK_INT >= 36 - - - val isRangingPermissionGranted: Boolean - get() = isRangingPermissionAvailable && - ContextCompat.checkSelfPermission( - context, - Manifest.permission.RANGING - ) == PackageManager.PERMISSION_GRANTED - - fun isRangingPermissionDenied(): Boolean { - return dataProvider.isBaklavaOrAbove && - dataProvider.isRangingPermissionRequested && // Ranging permission was requested. - !isRangingPermissionGranted // Ranging permission is not granted - && !context.findActivity() - ?.shouldShowRequestPermissionRationale(Manifest.permission.RANGING)!! - - } - - /** - * Finds the activity from the given context. - * - * https://github.com/google/accompanist/blob/6611ebda55eb2948eca9e1c89c2519e80300855a/permissions/src/main/java/com/google/accompanist/permissions/PermissionsUtil.kt#L99 - * - * @throws IllegalStateException if no activity was found. - * @return the activity. - */ - private fun Context.findActivity(): Activity? { - return try { - var context = this - while (context is ContextWrapper) { - if (context is Activity) return context - context = context.baseContext - } - null // no activity found - } catch (e: Exception) { - null - } - } -} \ No newline at end of file diff --git a/permissions-ranging/src/main/java/no/nordicsemi/android/permissions_ranging/view/RangingPermissionRequestView.kt b/permissions-ranging/src/main/java/no/nordicsemi/android/permissions_ranging/view/RangingPermissionRequestView.kt deleted file mode 100644 index 72a95df87..000000000 --- a/permissions-ranging/src/main/java/no/nordicsemi/android/permissions_ranging/view/RangingPermissionRequestView.kt +++ /dev/null @@ -1,49 +0,0 @@ -package no.nordicsemi.android.permissions_ranging.view - -import android.Manifest -import android.os.Build -import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect -import androidx.hilt.navigation.compose.hiltViewModel -import com.google.accompanist.permissions.ExperimentalPermissionsApi -import com.google.accompanist.permissions.PermissionStatus -import com.google.accompanist.permissions.isGranted -import com.google.accompanist.permissions.rememberPermissionState -import no.nordicsemi.android.permissions_ranging.viewmodel.RangingPermissionViewModel - -@OptIn(ExperimentalPermissionsApi::class) -@Composable -internal fun RangingPermissionRequestView( - content: @Composable (Boolean) -> Unit, -) { - val rangingPermissionViewModel = hiltViewModel() - - val permission = if (Build.VERSION.SDK_INT >= 36) - Manifest.permission.RANGING else null - - val rangingPermission = permission?.let { - rememberPermissionState(it) - } - - if (rangingPermission != null) { - when (rangingPermission.status) { - is PermissionStatus.Denied -> { - LaunchedEffect(!rangingPermission.status.isGranted) { - rangingPermissionViewModel.markRangingPermissionRequested() - rangingPermission.launchPermissionRequest() - if (!rangingPermission.status.isGranted) { - rangingPermissionViewModel.markRangingPermissionDenied() - } - rangingPermissionViewModel.refreshRangingPermissionState() - } - content(rangingPermission.status.isGranted) - } - - PermissionStatus.Granted -> content(true) - } - } else { - rangingPermissionViewModel.refreshRangingPermissionState() - content(true) - } - -} \ No newline at end of file diff --git a/permissions-ranging/src/main/java/no/nordicsemi/android/permissions_ranging/viewmodel/RangingPermissionViewModel.kt b/permissions-ranging/src/main/java/no/nordicsemi/android/permissions_ranging/viewmodel/RangingPermissionViewModel.kt deleted file mode 100644 index 87a3103dd..000000000 --- a/permissions-ranging/src/main/java/no/nordicsemi/android/permissions_ranging/viewmodel/RangingPermissionViewModel.kt +++ /dev/null @@ -1,37 +0,0 @@ -package no.nordicsemi.android.permissions_ranging.viewmodel - -import android.app.Activity -import androidx.lifecycle.ViewModel -import androidx.lifecycle.viewModelScope -import dagger.hilt.android.lifecycle.HiltViewModel -import kotlinx.coroutines.flow.SharingStarted -import kotlinx.coroutines.flow.stateIn -import no.nordicsemi.android.permissions_ranging.repository.RangingStateManager -import no.nordicsemi.android.permissions_ranging.utils.RangingNotAvailableReason -import no.nordicsemi.android.permissions_ranging.utils.RangingPermissionState -import javax.inject.Inject - -@HiltViewModel -internal class RangingPermissionViewModel @Inject constructor( - private val rangingStateManager: RangingStateManager, -) : ViewModel() { - fun requestRangingPermission(activity: Activity) = - rangingStateManager.rangingPermissionState(activity) - .stateIn( - viewModelScope, - SharingStarted.Lazily, - RangingPermissionState.NotAvailable(RangingNotAvailableReason.NOT_AVAILABLE), - ) - - fun refreshRangingPermissionState() { - rangingStateManager.refreshRangingPermissionState() - } - - fun markRangingPermissionRequested() { - rangingStateManager.markRangingPermissionAsRequested() - } - - fun markRangingPermissionDenied() { - rangingStateManager.isRangingPermissionDenied() - } -} \ No newline at end of file From b3b267c663f7db521cb66d600073601b17144a35 Mon Sep 17 00:00:00 2001 From: himalia416 Date: Mon, 6 Oct 2025 16:13:19 +0200 Subject: [PATCH 08/11] Fixed DFU redirection screen --- .../main/res/drawable/ic_device_manager.xml | 19 +++ .../src/main/res/drawable/ic_dfu.xml | 0 .../android/toolbox/profile/ProfileScreen.kt | 2 +- .../toolbox/profile/view/dfu/DFUScreen.kt | 135 ++++++++---------- .../toolbox/profile/viewmodel/DFUViewModel.kt | 66 +++++++++ .../res/drawable/google_play_2022_icon.xml | 18 +++ .../toolbox/profile/data/DFUServiceData.kt | 56 ++++++-- .../toolbox/profile/manager/DFUManager.kt | 41 ++++-- .../manager/repository/DFURepository.kt | 14 +- 9 files changed, 252 insertions(+), 99 deletions(-) create mode 100644 lib_utils/src/main/res/drawable/ic_device_manager.xml rename {profile => lib_utils}/src/main/res/drawable/ic_dfu.xml (100%) create mode 100644 profile/src/main/java/no/nordicsemi/android/toolbox/profile/viewmodel/DFUViewModel.kt create mode 100644 profile/src/main/res/drawable/google_play_2022_icon.xml diff --git a/lib_utils/src/main/res/drawable/ic_device_manager.xml b/lib_utils/src/main/res/drawable/ic_device_manager.xml new file mode 100644 index 000000000..891d87d9d --- /dev/null +++ b/lib_utils/src/main/res/drawable/ic_device_manager.xml @@ -0,0 +1,19 @@ + + + + + diff --git a/profile/src/main/res/drawable/ic_dfu.xml b/lib_utils/src/main/res/drawable/ic_dfu.xml similarity index 100% rename from profile/src/main/res/drawable/ic_dfu.xml rename to lib_utils/src/main/res/drawable/ic_dfu.xml diff --git a/profile/src/main/java/no/nordicsemi/android/toolbox/profile/ProfileScreen.kt b/profile/src/main/java/no/nordicsemi/android/toolbox/profile/ProfileScreen.kt index 62370305f..315498a95 100644 --- a/profile/src/main/java/no/nordicsemi/android/toolbox/profile/ProfileScreen.kt +++ b/profile/src/main/java/no/nordicsemi/android/toolbox/profile/ProfileScreen.kt @@ -208,7 +208,7 @@ internal fun DeviceConnectedView( Profile.BATTERY -> BatteryScreen() Profile.THROUGHPUT -> ThroughputScreen(state.maxValueLength) Profile.UART -> UARTScreen(state.maxValueLength) - Profile.DFU -> DFUScreen(modifier = Modifier.padding(16.dp)) + Profile.DFU -> DFUScreen() } } } diff --git a/profile/src/main/java/no/nordicsemi/android/toolbox/profile/view/dfu/DFUScreen.kt b/profile/src/main/java/no/nordicsemi/android/toolbox/profile/view/dfu/DFUScreen.kt index 94d7682c8..f81db6e9c 100644 --- a/profile/src/main/java/no/nordicsemi/android/toolbox/profile/view/dfu/DFUScreen.kt +++ b/profile/src/main/java/no/nordicsemi/android/toolbox/profile/view/dfu/DFUScreen.kt @@ -7,110 +7,93 @@ import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size -import androidx.compose.foundation.layout.widthIn -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.Download import androidx.compose.material3.Button import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.OutlinedCard import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalUriHandler import androidx.compose.ui.res.painterResource import androidx.compose.ui.text.style.TextAlign -import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp -import no.nordicsemi.android.common.theme.NordicTheme +import androidx.hilt.navigation.compose.hiltViewModel +import androidx.lifecycle.compose.collectAsStateWithLifecycle import no.nordicsemi.android.toolbox.profile.R +import no.nordicsemi.android.toolbox.profile.viewmodel.DFUViewModel @Composable -internal fun DFUScreen( - modifier: Modifier = Modifier -) { - Column( - modifier = Modifier - .fillMaxSize() - .then(modifier), - horizontalAlignment = Alignment.CenterHorizontally, - verticalArrangement = Arrangement.spacedBy(16.dp) - ) { - OutlinedCard( +internal fun DFUScreen() { + val dfuViewModel = hiltViewModel() + val dfuServiceState by dfuViewModel.dfuServiceState.collectAsStateWithLifecycle() + val context = LocalContext.current + val uriHandler = LocalUriHandler.current + dfuServiceState.dfuAppName?.let { dfuApp -> + val intent = context.packageManager.getLaunchIntentForPackage(dfuApp.packageName) + val description = intent?.let { "Open ${dfuApp.appName}" } ?: "Download from Play Store" + + Column( modifier = Modifier - .widthIn(max = 460.dp), + .fillMaxSize(), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.spacedBy(16.dp) ) { - Column( - modifier = Modifier - .fillMaxWidth() - .padding(16.dp), - verticalArrangement = Arrangement.spacedBy(16.dp), - horizontalAlignment = Alignment.CenterHorizontally, - ) { - Icon( - painter = painterResource(R.drawable.ic_dfu), - contentDescription = null, - tint = MaterialTheme.colorScheme.onSurfaceVariant, - modifier = Modifier.size(48.dp) - ) + OutlinedCard { + Column( + modifier = Modifier + .fillMaxWidth() + .padding(16.dp), + verticalArrangement = Arrangement.spacedBy(16.dp), + horizontalAlignment = Alignment.CenterHorizontally, + ) { + Icon( + painter = painterResource(R.drawable.ic_dfu), + contentDescription = null, + tint = MaterialTheme.colorScheme.onSurfaceVariant, + modifier = Modifier.size(56.dp) + ) - Text( - text = "DFU is not supported", - style = MaterialTheme.typography.titleMedium - ) + Text( + text = "DFU is not supported", + style = MaterialTheme.typography.titleMedium + ) - Text( - text = "DFU service is not available in the current version of the app. " + - "Please use the DFU app from Nordic Semiconductor to update your device’s firmware.", - textAlign = TextAlign.Center, - style = MaterialTheme.typography.bodyMedium - ) + Text( + text = "DFU service is not available in the current version of the app. " + + "Please use the DFU app from Nordic Semiconductor to update your device’s firmware.", + textAlign = TextAlign.Center, + style = MaterialTheme.typography.bodyMedium + ) + } } - } - - val uriHandler = LocalUriHandler.current - val context = LocalContext.current - val packageManger = context.packageManager - val intent = packageManger.getLaunchIntentForPackage(DFU_PACKAGE_NAME) - - val description = intent?.let { - "Open DFU" - } ?: "Download from Play Store" - Button( - onClick = { - if (intent != null) { - context.startActivity(intent) - } else { - uriHandler.openUri(DFU_APP_LINK) + Button( + onClick = { + intent?.let { context.startActivity(it) } + ?: uriHandler.openUri(dfuApp.appLink) } - }, - ) { - Row { - intent?.let { + ) { + Row(verticalAlignment = Alignment.CenterVertically) { + val icon = intent?.let { dfuApp.appIcon } ?: R.drawable.google_play_2022_icon + val size = if (intent != null) 56.dp else 28.dp + Icon( - painter = painterResource(R.drawable.ic_dfu), + painter = painterResource(icon), contentDescription = null, modifier = Modifier - .size(24.dp) - .padding(end = 8.dp) + .size(size) + .padding(end = 8.dp), + tint = if (intent == null) Color.Unspecified else MaterialTheme.colorScheme.onPrimary ) - } ?: Icon(imageVector = Icons.Default.Download, contentDescription = null) - Text(text = description) - } + Text(text = description) + } + } } } } - -@Preview(showBackground = true) -@Composable -private fun DeviceDisconnectedViewPreview() { - NordicTheme { - DFUScreen( - modifier = Modifier.padding(16.dp) - ) - } -} diff --git a/profile/src/main/java/no/nordicsemi/android/toolbox/profile/viewmodel/DFUViewModel.kt b/profile/src/main/java/no/nordicsemi/android/toolbox/profile/viewmodel/DFUViewModel.kt new file mode 100644 index 000000000..dca5a35cd --- /dev/null +++ b/profile/src/main/java/no/nordicsemi/android/toolbox/profile/viewmodel/DFUViewModel.kt @@ -0,0 +1,66 @@ +package no.nordicsemi.android.toolbox.profile.viewmodel + +import androidx.lifecycle.SavedStateHandle +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.launch +import no.nordicsemi.android.common.navigation.Navigator +import no.nordicsemi.android.common.navigation.viewmodel.SimpleNavigationViewModel +import no.nordicsemi.android.toolbox.lib.utils.Profile +import no.nordicsemi.android.toolbox.profile.ProfileDestinationId +import no.nordicsemi.android.toolbox.profile.data.DFUServiceData +import no.nordicsemi.android.toolbox.profile.manager.repository.DFURepository +import no.nordicsemi.android.toolbox.profile.repository.DeviceRepository +import javax.inject.Inject + +@HiltViewModel +internal class DFUViewModel @Inject constructor( + private val deviceRepository: DeviceRepository, + navigator: Navigator, + savedStateHandle: SavedStateHandle, +) : SimpleNavigationViewModel(navigator, savedStateHandle) { + val address = parameterOf(ProfileDestinationId) + + // StateFlow to hold the selected temperature unit + private val _dfuServiceState = MutableStateFlow(DFUServiceData()) + val dfuServiceState = _dfuServiceState.asStateFlow() + + init { + observeDFUProfile() + } + + /** + * Observes the [DeviceRepository.profileHandlerFlow] from the [deviceRepository] that contains [Profile.DFU]. + */ + private fun observeDFUProfile() = viewModelScope.launch { + deviceRepository.profileHandlerFlow + .onEach { mapOfPeripheralProfiles -> + mapOfPeripheralProfiles.forEach { (peripheral, profiles) -> + if (peripheral.address == address) { + profiles.filter { it.profile == Profile.DFU } + .forEach { _ -> + startDFUService(peripheral.address) + } + } + } + }.launchIn(this) + } + + /** + * Starts the DFU Service and observes data changes. + * + * @param address The address of the peripheral device. + */ + private fun startDFUService(address: String) = DFURepository.getData(address) + .onEach { dFUServiceData -> + _dfuServiceState.value = _dfuServiceState.value.copy( + profile = dFUServiceData.profile, + dfuAppName = dFUServiceData.dfuAppName + ) + }.launchIn(viewModelScope) + +} \ No newline at end of file diff --git a/profile/src/main/res/drawable/google_play_2022_icon.xml b/profile/src/main/res/drawable/google_play_2022_icon.xml new file mode 100644 index 000000000..75073ebb4 --- /dev/null +++ b/profile/src/main/res/drawable/google_play_2022_icon.xml @@ -0,0 +1,18 @@ + + + + + + diff --git a/profile_data/src/main/java/no/nordicsemi/android/toolbox/profile/data/DFUServiceData.kt b/profile_data/src/main/java/no/nordicsemi/android/toolbox/profile/data/DFUServiceData.kt index 45698a2aa..ff047fb6d 100644 --- a/profile_data/src/main/java/no/nordicsemi/android/toolbox/profile/data/DFUServiceData.kt +++ b/profile_data/src/main/java/no/nordicsemi/android/toolbox/profile/data/DFUServiceData.kt @@ -1,16 +1,56 @@ package no.nordicsemi.android.toolbox.profile.data +import androidx.annotation.DrawableRes import no.nordicsemi.android.toolbox.lib.utils.Profile +import no.nordicsemi.android.toolbox.lib.utils.R + +internal const val DFU_PACKAGE_NAME = "no.nordicsemi.android.dfu" +internal const val DFU_APP_LINK = + "https://play.google.com/store/apps/details?id=no.nordicsemi.android.dfu" + +internal const val SMP_PACKAGE_NAME = "no.nordicsemi.android.nrfconnectdevicemanager" +internal const val SMP_APP_LINK = + "https://play.google.com/store/apps/details?id=no.nordicsemi.android.nrfconnectdevicemanager" data class DFUServiceData( override val profile: Profile = Profile.DFU, - val dfuAppName : DFUsAvailable? = null, -): ProfileServiceData() + val dfuAppName: DFUsAvailable? = null, +) : ProfileServiceData() -enum class DFUsAvailable { - DFU_SERVICE, - SMP_SERVICE, - MDS_SERVICE, - LEGACY_DFU_SERVICE, - EXPERIMENTAL_BUTTONLESS_DFU_SERVICE; +enum class DFUsAvailable( + val packageName: String, + val appLink: String, + val appName: String, + @DrawableRes val appIcon: Int +) { + DFU_SERVICE( + packageName = DFU_PACKAGE_NAME, + appLink = DFU_APP_LINK, + appName = "DFU", + appIcon = R.drawable.ic_dfu + ), + SMP_SERVICE( + packageName = SMP_PACKAGE_NAME, + appLink = SMP_APP_LINK, + appName = "nRF Connect Device Manager", + appIcon = R.drawable.ic_device_manager + ), + MDS_SERVICE( + packageName = SMP_PACKAGE_NAME, + appLink = SMP_APP_LINK, + appName = "nRF Connect Device Manager", + appIcon = R.drawable.ic_device_manager + ), + LEGACY_DFU_SERVICE( + packageName = DFU_PACKAGE_NAME, + appLink = DFU_APP_LINK, + appName = "DFU", + appIcon = R.drawable.ic_dfu + ), + EXPERIMENTAL_BUTTONLESS_DFU_SERVICE( + packageName = DFU_PACKAGE_NAME, + appLink = DFU_APP_LINK, + appName = "DFU", + appIcon = R.drawable.ic_dfu + ) } \ No newline at end of file diff --git a/profile_manager/src/main/java/no/nordicsemi/android/toolbox/profile/manager/DFUManager.kt b/profile_manager/src/main/java/no/nordicsemi/android/toolbox/profile/manager/DFUManager.kt index 837ae970a..767d93593 100644 --- a/profile_manager/src/main/java/no/nordicsemi/android/toolbox/profile/manager/DFUManager.kt +++ b/profile_manager/src/main/java/no/nordicsemi/android/toolbox/profile/manager/DFUManager.kt @@ -23,17 +23,38 @@ internal class DFUManager : ServiceManager { remoteService: RemoteService, scope: CoroutineScope ) { - val appName = when (remoteService.uuid) { - DFU_SERVICE_UUID.toKotlinUuid() -> DFUsAvailable.DFU_SERVICE - SMP_SERVICE_UUID.toKotlinUuid() -> DFUsAvailable.SMP_SERVICE - LEGACY_DFU_SERVICE_UUID.toKotlinUuid() -> DFUsAvailable.LEGACY_DFU_SERVICE - EXPERIMENTAL_BUTTONLESS_DFU_SERVICE_UUID.toKotlinUuid() -> DFUsAvailable.EXPERIMENTAL_BUTTONLESS_DFU_SERVICE - MDS_SERVICE_UUID.toKotlinUuid() -> DFUsAvailable.MDS_SERVICE - - else -> null + try { + when (remoteService.uuid) { + DFU_SERVICE_UUID.toKotlinUuid() -> DFURepository.updateAppName( + deviceId, + DFUsAvailable.DFU_SERVICE + ) + + SMP_SERVICE_UUID.toKotlinUuid() -> DFURepository.updateAppName( + deviceId, + DFUsAvailable.SMP_SERVICE + ) + + LEGACY_DFU_SERVICE_UUID.toKotlinUuid() -> DFURepository.updateAppName( + deviceId, + DFUsAvailable.LEGACY_DFU_SERVICE + ) + + EXPERIMENTAL_BUTTONLESS_DFU_SERVICE_UUID.toKotlinUuid() -> DFURepository.updateAppName( + deviceId, + DFUsAvailable.EXPERIMENTAL_BUTTONLESS_DFU_SERVICE + ) + + MDS_SERVICE_UUID.toKotlinUuid() -> DFURepository.updateAppName( + deviceId, + DFUsAvailable.MDS_SERVICE + ) + + else -> null + } + } catch (_: Exception) { + DFURepository.clear(deviceId) } - if (appName != null) - DFURepository.updateAppName(deviceId, appName) } } \ No newline at end of file diff --git a/profile_manager/src/main/java/no/nordicsemi/android/toolbox/profile/manager/repository/DFURepository.kt b/profile_manager/src/main/java/no/nordicsemi/android/toolbox/profile/manager/repository/DFURepository.kt index 8bbea5874..7fbd68640 100644 --- a/profile_manager/src/main/java/no/nordicsemi/android/toolbox/profile/manager/repository/DFURepository.kt +++ b/profile_manager/src/main/java/no/nordicsemi/android/toolbox/profile/manager/repository/DFURepository.kt @@ -9,12 +9,18 @@ import no.nordicsemi.android.toolbox.profile.data.DFUsAvailable object DFURepository { private val _dataMap = mutableMapOf>() - fun getData(deviceId: String): Flow { - return _dataMap.getOrPut(deviceId) { MutableStateFlow(DFUServiceData()) } - } + fun getData(deviceId: String): Flow = + _dataMap.getOrPut(deviceId) { MutableStateFlow(DFUServiceData()) } + fun updateAppName(deviceId: String, appName: DFUsAvailable) { - _dataMap[deviceId]?.update { it.copy(dfuAppName = appName) } + _dataMap[deviceId]?.let { + it.update { dFUServiceData -> + dFUServiceData.copy(dfuAppName = appName) + } + } ?: run { + _dataMap[deviceId] = MutableStateFlow(DFUServiceData(dfuAppName = appName)) + } } fun clear(deviceId: String) { From ae573a32c4379ef514bd3fd9d6e246eaf678c6d1 Mon Sep 17 00:00:00 2001 From: himalia416 Date: Mon, 6 Oct 2025 16:27:02 +0200 Subject: [PATCH 09/11] Fixed vector points --- .../main/res/drawable/ic_device_manager.xml | 38 ++++++++++--------- 1 file changed, 21 insertions(+), 17 deletions(-) diff --git a/lib_utils/src/main/res/drawable/ic_device_manager.xml b/lib_utils/src/main/res/drawable/ic_device_manager.xml index 891d87d9d..854e204e0 100644 --- a/lib_utils/src/main/res/drawable/ic_device_manager.xml +++ b/lib_utils/src/main/res/drawable/ic_device_manager.xml @@ -1,19 +1,23 @@ - - - + android:width="80dp" + android:height="80dp" + android:viewportWidth="600" + android:viewportHeight="600"> + + + + + From af4ba32b15a0f31980e809f9625b76c2c8976da4 Mon Sep 17 00:00:00 2001 From: himalia416 Date: Mon, 6 Oct 2025 16:27:15 +0200 Subject: [PATCH 10/11] Removed unused file --- .../toolbox/profile/view/dfu/DfuUiMapper.kt | 32 ------------------- 1 file changed, 32 deletions(-) delete mode 100644 profile/src/main/java/no/nordicsemi/android/toolbox/profile/view/dfu/DfuUiMapper.kt diff --git a/profile/src/main/java/no/nordicsemi/android/toolbox/profile/view/dfu/DfuUiMapper.kt b/profile/src/main/java/no/nordicsemi/android/toolbox/profile/view/dfu/DfuUiMapper.kt deleted file mode 100644 index d57f35793..000000000 --- a/profile/src/main/java/no/nordicsemi/android/toolbox/profile/view/dfu/DfuUiMapper.kt +++ /dev/null @@ -1,32 +0,0 @@ -package no.nordicsemi.android.toolbox.profile.view.dfu - -import no.nordicsemi.android.toolbox.profile.data.DFUsAvailable - - -internal const val DFU_PACKAGE_NAME = "no.nordicsemi.android.dfu" -internal const val DFU_APP_LINK = - "https://play.google.com/store/apps/details?id=no.nordicsemi.android.dfu" - -internal const val SMP_PACKAGE_NAME = "no.nordicsemi.android.nrfconnectdevicemanager" -internal const val SMP_APP_LINK = - "https://play.google.com/store/apps/details?id=no.nordicsemi.android.nrfconnectdevicemanager" - -internal fun DFUsAvailable.getApp(): String { - return when (this) { - DFUsAvailable.DFU_SERVICE -> DFU_APP_LINK - DFUsAvailable.SMP_SERVICE -> SMP_APP_LINK - DFUsAvailable.MDS_SERVICE -> SMP_APP_LINK - DFUsAvailable.LEGACY_DFU_SERVICE -> DFU_APP_LINK - DFUsAvailable.EXPERIMENTAL_BUTTONLESS_DFU_SERVICE -> DFU_APP_LINK - } -} - -internal fun DFUsAvailable.getPackageName(): String { - return when (this) { - DFUsAvailable.DFU_SERVICE -> DFU_PACKAGE_NAME - DFUsAvailable.SMP_SERVICE -> SMP_PACKAGE_NAME - DFUsAvailable.MDS_SERVICE -> SMP_PACKAGE_NAME - DFUsAvailable.LEGACY_DFU_SERVICE -> DFU_PACKAGE_NAME - DFUsAvailable.EXPERIMENTAL_BUTTONLESS_DFU_SERVICE -> DFU_PACKAGE_NAME - } -} From 02f7e5d496df49052378387decf9206dae8695e0 Mon Sep 17 00:00:00 2001 From: himalia416 Date: Mon, 6 Oct 2025 16:58:54 +0200 Subject: [PATCH 11/11] use string resource --- lib_utils/src/main/res/values/string.xml | 13 ++++++++ .../toolbox/profile/view/dfu/DFUScreen.kt | 28 ++++++++++++----- profile/src/main/res/values/dfuStrings.xml | 8 +++++ .../toolbox/profile/data/DFUServiceData.kt | 31 ++++++++++++------- 4 files changed, 61 insertions(+), 19 deletions(-) create mode 100644 lib_utils/src/main/res/values/string.xml create mode 100644 profile/src/main/res/values/dfuStrings.xml diff --git a/lib_utils/src/main/res/values/string.xml b/lib_utils/src/main/res/values/string.xml new file mode 100644 index 000000000..508ae8e69 --- /dev/null +++ b/lib_utils/src/main/res/values/string.xml @@ -0,0 +1,13 @@ + + + Device Firmware Update + DFU + nRF Connect Device Manager + SMP + nRF Connect Device Manager + MDS + Legacy DFU + Legacy DFU + Buttonless DFU + Buttonless DFU + \ No newline at end of file diff --git a/profile/src/main/java/no/nordicsemi/android/toolbox/profile/view/dfu/DFUScreen.kt b/profile/src/main/java/no/nordicsemi/android/toolbox/profile/view/dfu/DFUScreen.kt index f81db6e9c..c66c3a507 100644 --- a/profile/src/main/java/no/nordicsemi/android/toolbox/profile/view/dfu/DFUScreen.kt +++ b/profile/src/main/java/no/nordicsemi/android/toolbox/profile/view/dfu/DFUScreen.kt @@ -20,6 +20,7 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalUriHandler import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import androidx.hilt.navigation.compose.hiltViewModel @@ -33,9 +34,17 @@ internal fun DFUScreen() { val dfuServiceState by dfuViewModel.dfuServiceState.collectAsStateWithLifecycle() val context = LocalContext.current val uriHandler = LocalUriHandler.current + dfuServiceState.dfuAppName?.let { dfuApp -> val intent = context.packageManager.getLaunchIntentForPackage(dfuApp.packageName) - val description = intent?.let { "Open ${dfuApp.appName}" } ?: "Download from Play Store" + val description = + intent?.let { + stringResource( + R.string.dfu_description_open, + stringResource(dfuApp.appName) + ) + } + ?: stringResource(R.string.dfu_description_download) Column( modifier = Modifier @@ -52,20 +61,26 @@ internal fun DFUScreen() { horizontalAlignment = Alignment.CenterHorizontally, ) { Icon( - painter = painterResource(R.drawable.ic_dfu), + painter = painterResource(dfuApp.appIcon), contentDescription = null, tint = MaterialTheme.colorScheme.onSurfaceVariant, modifier = Modifier.size(56.dp) ) Text( - text = "DFU is not supported", + text = stringResource( + R.string.dfu_not_supported_title, + stringResource(dfuApp.appShortName) + ), style = MaterialTheme.typography.titleMedium ) Text( - text = "DFU service is not available in the current version of the app. " + - "Please use the DFU app from Nordic Semiconductor to update your device’s firmware.", + text = stringResource( + R.string.dfu_not_supported_text, + stringResource(dfuApp.appShortName), + stringResource(dfuApp.appName) + ), textAlign = TextAlign.Center, style = MaterialTheme.typography.bodyMedium ) @@ -80,13 +95,12 @@ internal fun DFUScreen() { ) { Row(verticalAlignment = Alignment.CenterVertically) { val icon = intent?.let { dfuApp.appIcon } ?: R.drawable.google_play_2022_icon - val size = if (intent != null) 56.dp else 28.dp Icon( painter = painterResource(icon), contentDescription = null, modifier = Modifier - .size(size) + .size(40.dp) .padding(end = 8.dp), tint = if (intent == null) Color.Unspecified else MaterialTheme.colorScheme.onPrimary ) diff --git a/profile/src/main/res/values/dfuStrings.xml b/profile/src/main/res/values/dfuStrings.xml new file mode 100644 index 000000000..ae269ba4a --- /dev/null +++ b/profile/src/main/res/values/dfuStrings.xml @@ -0,0 +1,8 @@ + + + Open %s + Download from Play Store + %s is not supported + %1s service is not available in the current version of the app. Please use the %2s app from Nordic Semiconductor to update your device’s firmware. + + \ No newline at end of file diff --git a/profile_data/src/main/java/no/nordicsemi/android/toolbox/profile/data/DFUServiceData.kt b/profile_data/src/main/java/no/nordicsemi/android/toolbox/profile/data/DFUServiceData.kt index ff047fb6d..ade594efb 100644 --- a/profile_data/src/main/java/no/nordicsemi/android/toolbox/profile/data/DFUServiceData.kt +++ b/profile_data/src/main/java/no/nordicsemi/android/toolbox/profile/data/DFUServiceData.kt @@ -1,6 +1,7 @@ package no.nordicsemi.android.toolbox.profile.data import androidx.annotation.DrawableRes +import androidx.annotation.StringRes import no.nordicsemi.android.toolbox.lib.utils.Profile import no.nordicsemi.android.toolbox.lib.utils.R @@ -20,37 +21,43 @@ data class DFUServiceData( enum class DFUsAvailable( val packageName: String, val appLink: String, - val appName: String, - @DrawableRes val appIcon: Int + @param:StringRes val appName: Int, + @param:DrawableRes val appIcon: Int, + @param:StringRes val appShortName: Int, ) { DFU_SERVICE( packageName = DFU_PACKAGE_NAME, appLink = DFU_APP_LINK, - appName = "DFU", - appIcon = R.drawable.ic_dfu + appName = R.string.dfu_app_name, + appIcon = R.drawable.ic_dfu, + appShortName = R.string.dfu_short_name, ), SMP_SERVICE( packageName = SMP_PACKAGE_NAME, appLink = SMP_APP_LINK, - appName = "nRF Connect Device Manager", - appIcon = R.drawable.ic_device_manager + appName = R.string.smp_app_name, + appIcon = R.drawable.ic_device_manager, + appShortName = R.string.smp_short_name, ), MDS_SERVICE( packageName = SMP_PACKAGE_NAME, appLink = SMP_APP_LINK, - appName = "nRF Connect Device Manager", - appIcon = R.drawable.ic_device_manager + appName = R.string.mds_app_name, + appIcon = R.drawable.ic_device_manager, + appShortName = R.string.mds_app_name, ), LEGACY_DFU_SERVICE( packageName = DFU_PACKAGE_NAME, appLink = DFU_APP_LINK, - appName = "DFU", - appIcon = R.drawable.ic_dfu + appName = R.string.legacy_dfu_app_name, + appIcon = R.drawable.ic_dfu, + appShortName = R.string.legacy_dfu_short_name, ), EXPERIMENTAL_BUTTONLESS_DFU_SERVICE( packageName = DFU_PACKAGE_NAME, appLink = DFU_APP_LINK, - appName = "DFU", - appIcon = R.drawable.ic_dfu + appName = R.string.buttonless_dfu_app_name, + appIcon = R.drawable.ic_dfu, + appShortName = R.string.buttonless_dfu_short_name, ) } \ No newline at end of file