Remove hex & bin files from DFU.

This commit is contained in:
Sylwester Zieliński
2022-01-11 11:00:30 +01:00
parent 0993799e42
commit 27e71dc0a3
16 changed files with 61 additions and 232 deletions

View File

@@ -9,15 +9,10 @@ internal data class NoFileSelectedState(
) : DFUData() ) : DFUData()
internal data class FileReadyState( internal data class FileReadyState(
val file: DFUFile, val file: ZipFile,
val device: DiscoveredBluetoothDevice val device: DiscoveredBluetoothDevice
) : DFUData() ) : DFUData()
internal data class HexFileLoadedState(
val file: PartialHexFile,
val isDatFileError: Boolean = false
) : DFUData()
internal data class FileInstallingState( internal data class FileInstallingState(
val status: DFUServiceStatus = Idle val status: DFUServiceStatus = Idle
) : DFUData() ) : DFUData()

View File

@@ -2,26 +2,7 @@ package no.nordicsemi.dfu.data
import android.net.Uri import android.net.Uri
sealed class DFUFile { data class ZipFile(
abstract val fileType: DFUFileType
}
data class ZipFile(val data: FileData) : DFUFile() {
override val fileType: DFUFileType = DFUFileType.TYPE_AUTO
}
data class PartialHexFile(
val data: FileData,
val fileType: DFUFileType
)
data class FullHexFile(
val data: FileData,
val datFileData: FileData,
override val fileType: DFUFileType
) : DFUFile()
data class FileData(
val uri: Uri, val uri: Uri,
val name: String, val name: String,
val path: String?, val path: String?,

View File

@@ -15,7 +15,7 @@ class DFUFileManager @Inject constructor(
private val TAG = "DFU_FILE_MANAGER" private val TAG = "DFU_FILE_MANAGER"
fun createFile(uri: Uri): FileData? { fun createFile(uri: Uri): ZipFile? {
return try { return try {
createFromFile(uri) createFromFile(uri)
} catch (e: Exception) { } catch (e: Exception) {
@@ -29,12 +29,12 @@ class DFUFileManager @Inject constructor(
} }
} }
private fun createFromFile(uri: Uri): FileData { private fun createFromFile(uri: Uri): ZipFile {
val file = uri.toFile() val file = uri.toFile()
return FileData(uri, file.name, file.path, file.length()) return ZipFile(uri, file.name, file.path, file.length())
} }
private fun createFromContentResolver(uri: Uri): FileData? { private fun createFromContentResolver(uri: Uri): ZipFile? {
val data = context.contentResolver.query(uri, null, null, null, null) val data = context.contentResolver.query(uri, null, null, null, null)
return if (data != null && data.moveToNext()) { return if (data != null && data.moveToNext()) {
@@ -53,7 +53,7 @@ class DFUFileManager @Inject constructor(
data.close() data.close()
FileData(uri, fileName, filePath, fileSize.toLong()) ZipFile(uri, fileName, filePath, fileSize.toLong())
} else { } else {
Log.d(TAG, "Data loaded from ContentResolver is empty.") Log.d(TAG, "Data loaded from ContentResolver is empty.")
null null

View File

@@ -1,16 +0,0 @@
package no.nordicsemi.dfu.data
import no.nordicsemi.android.dfu.DfuBaseService
enum class DFUFileType(val id: Int) {
TYPE_AUTO(DfuBaseService.TYPE_AUTO),
TYPE_SOFT_DEVICE(DfuBaseService.TYPE_SOFT_DEVICE),
TYPE_BOOTLOADER(DfuBaseService.TYPE_BOOTLOADER),
TYPE_APPLICATION(DfuBaseService.TYPE_APPLICATION);
companion object {
fun create(id: Int): DFUFileType? {
return values().find { it.id == id }
}
}
}

View File

@@ -5,7 +5,6 @@ import dagger.hilt.android.qualifiers.ApplicationContext
import no.nordicsemi.android.dfu.DfuServiceInitiator import no.nordicsemi.android.dfu.DfuServiceInitiator
import no.nordicsemi.android.service.SelectedBluetoothDeviceHolder import no.nordicsemi.android.service.SelectedBluetoothDeviceHolder
import no.nordicsemi.dfu.repository.DFUService import no.nordicsemi.dfu.repository.DFUService
import no.nordicsemi.ui.scanner.ui.exhaustive
import javax.inject.Inject import javax.inject.Inject
class DFUManager @Inject constructor( class DFUManager @Inject constructor(
@@ -14,7 +13,7 @@ class DFUManager @Inject constructor(
private val deviceHolder: SelectedBluetoothDeviceHolder private val deviceHolder: SelectedBluetoothDeviceHolder
) { ) {
fun install(file: DFUFile) { fun install(file: ZipFile) {
val device = deviceHolder.device!! val device = deviceHolder.device!!
val starter = DfuServiceInitiator(device.address()) val starter = DfuServiceInitiator(device.address())
@@ -26,12 +25,7 @@ class DFUManager @Inject constructor(
.setPrepareDataObjectDelay(400) .setPrepareDataObjectDelay(400)
.setUnsafeExperimentalButtonlessServiceInSecureDfuEnabled(true) .setUnsafeExperimentalButtonlessServiceInSecureDfuEnabled(true)
when (file) { starter.setZip(file.uri, file.path)
is ZipFile -> starter.setZip(file.data.uri, file.data.path)
is FullHexFile -> starter.setBinOrHex(file.fileType.id, file.data.uri, file.data.path)
.setInitFile(file.datFileData.uri, file.datFileData.path)
}.exhaustive
starter.start(context, DFUService::class.java) starter.start(context, DFUService::class.java)
} }
} }

View File

@@ -20,24 +20,10 @@ internal class DFURepository @Inject constructor(
fun setZipFile(file: Uri) { fun setZipFile(file: Uri) {
val currentState = _data.value as NoFileSelectedState val currentState = _data.value as NoFileSelectedState
_data.value = fileManger.createFile(file)?.let { _data.value = fileManger.createFile(file)?.let {
FileReadyState(ZipFile(it), requireNotNull(deviceHolder.device)) FileReadyState(it, requireNotNull(deviceHolder.device))
} ?: currentState.copy(isError = true) } ?: currentState.copy(isError = true)
} }
fun setHexFile(file: Uri) {
val currentState = _data.value as NoFileSelectedState
_data.value = fileManger.createFile(file)?.let {
HexFileLoadedState(PartialHexFile(it, DFUFileType.TYPE_APPLICATION))
} ?: currentState.copy(isError = true)
}
fun setDatFile(file: Uri) {
val currentState = _data.value as HexFileLoadedState
_data.value = fileManger.createFile(file)?.let {
FileReadyState(FullHexFile(it, currentState.file.data, DFUFileType.TYPE_APPLICATION), requireNotNull(deviceHolder.device))
} ?: currentState.copy(isDatFileError = true)
}
fun setSuccess() { fun setSuccess() {
_data.value = UploadSuccessState _data.value = UploadSuccessState
} }

View File

@@ -14,7 +14,6 @@ internal fun DFUContentView(state: DFUData, onEvent: (DFUViewEvent) -> Unit) {
when (state) { when (state) {
is NoFileSelectedState -> DFUSelectMainFileView(state, onEvent) is NoFileSelectedState -> DFUSelectMainFileView(state, onEvent)
is FileReadyState -> DFUSummaryView(state, onEvent) is FileReadyState -> DFUSummaryView(state, onEvent)
is HexFileLoadedState -> DFUSelectDatFileView(state, onEvent)
UploadSuccessState -> DFUSuccessView(onEvent) UploadSuccessState -> DFUSuccessView(onEvent)
is UploadFailureState -> DFUErrorView(state, onEvent) is UploadFailureState -> DFUErrorView(state, onEvent)
is FileInstallingState -> DFUInstallingView(state, onEvent) is FileInstallingState -> DFUInstallingView(state, onEvent)

View File

@@ -21,18 +21,22 @@ import no.nordicsemi.dfu.data.UploadFailureState
internal fun DFUErrorView(state: UploadFailureState, onEvent: (DFUViewEvent) -> Unit) { internal fun DFUErrorView(state: UploadFailureState, onEvent: (DFUViewEvent) -> Unit) {
Column(horizontalAlignment = Alignment.CenterHorizontally) { Column(horizontalAlignment = Alignment.CenterHorizontally) {
ScreenSection { ScreenSection {
val errorColor = MaterialTheme.colorScheme.error
Icon( Icon(
painter = painterResource(id = R.drawable.ic_fail_circle), painter = painterResource(id = R.drawable.ic_fail_circle),
contentDescription = stringResource(id = R.string.dfu_failure_icon_description), contentDescription = stringResource(id = R.string.dfu_failure_icon_description),
tint = MaterialTheme.colorScheme.error tint = errorColor
) )
Spacer(modifier = Modifier.size(16.dp))
Spacer(modifier = Modifier.size(8.dp))
val error = state.message ?: stringResource(id = R.string.dfu_unknown_error) val error = state.message ?: stringResource(id = R.string.dfu_unknown_error)
Text( Text(
text = error, text = error,
color = MaterialTheme.colorScheme.error color = errorColor,
style = MaterialTheme.typography.titleLarge
) )
Spacer(modifier = Modifier.size(16.dp)) Spacer(modifier = Modifier.size(16.dp))

View File

@@ -1,19 +1,13 @@
package no.nordicsemi.dfu.view package no.nordicsemi.dfu.view
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.size
import androidx.compose.material3.Button
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import no.nordicsemi.android.material.you.CircularProgressIndicator import no.nordicsemi.android.material.you.CircularProgressIndicator
import no.nordicsemi.android.theme.view.ScreenSection import no.nordicsemi.android.theme.view.ScreenSection
import no.nordicsemi.dfu.R
import no.nordicsemi.dfu.data.FileInstallingState import no.nordicsemi.dfu.data.FileInstallingState
@Composable @Composable
@@ -24,19 +18,5 @@ internal fun DFUInstallingView(state: FileInstallingState, onEvent: (DFUViewEven
Spacer(modifier = Modifier.height(16.dp)) Spacer(modifier = Modifier.height(16.dp))
Text(text = state.status.toDisplayString()) Text(text = state.status.toDisplayString())
Spacer(modifier = Modifier.height(16.dp))
Row(horizontalArrangement = Arrangement.SpaceEvenly) {
Button(onClick = { onEvent(OnPauseButtonClick) }) {
Text(text = stringResource(id = R.string.dfu_pause))
}
Spacer(modifier = Modifier.size(16.dp))
Button(onClick = { onEvent(OnPauseButtonClick) }) {
Text(text = stringResource(id = R.string.dfu_stop))
}
}
} }
} }

View File

@@ -1,58 +0,0 @@
package no.nordicsemi.dfu.view
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Settings
import androidx.compose.material3.Button
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import no.nordicsemi.android.dfu.DfuBaseService
import no.nordicsemi.android.theme.view.ScreenSection
import no.nordicsemi.android.theme.view.SectionTitle
import no.nordicsemi.dfu.R
import no.nordicsemi.dfu.data.HexFileLoadedState
@Composable
internal fun DFUSelectDatFileView(state: HexFileLoadedState, onEvent: (DFUViewEvent) -> Unit) {
ScreenSection {
SectionTitle(
icon = Icons.Default.Settings,
title = stringResource(id = R.string.dfu_choose_file)
)
Spacer(modifier = Modifier.size(8.dp))
Text(
text = stringResource(id = R.string.dfu_choose_dat_info),
style = MaterialTheme.typography.bodyMedium
)
if (state.isDatFileError) {
Spacer(modifier = Modifier.size(8.dp))
Text(
text = stringResource(id = R.string.dfu_load_file_error),
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.error
)
}
Spacer(modifier = Modifier.size(8.dp))
val launcher = rememberLauncherForActivityResult(ActivityResultContracts.GetContent()) { uri ->
uri?.let { onEvent(OnDatFileSelected(it)) }
}
Button(onClick = { launcher.launch(DfuBaseService.MIME_TYPE_OCTET_STREAM) }) {
Text(text = stringResource(id = R.string.dfu_select_dat))
}
}
}

View File

@@ -2,11 +2,7 @@ package no.nordicsemi.dfu.view
import androidx.activity.compose.rememberLauncherForActivityResult import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.size
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Settings import androidx.compose.material.icons.filled.Settings
@@ -62,31 +58,13 @@ private fun ButtonsRow(onEvent: (DFUViewEvent) -> Unit) {
val fileType = rememberSaveable { mutableStateOf(DfuBaseService.MIME_TYPE_ZIP) } val fileType = rememberSaveable { mutableStateOf(DfuBaseService.MIME_TYPE_ZIP) }
val launcher = rememberLauncherForActivityResult(ActivityResultContracts.GetContent()) { uri -> val launcher = rememberLauncherForActivityResult(ActivityResultContracts.GetContent()) { uri ->
uri?.let { uri?.let { onEvent(OnZipFileSelected(it)) }
if (fileType.value == DfuBaseService.MIME_TYPE_ZIP) {
onEvent(OnZipFileSelected(it))
} else {
onEvent(OnHexFileSelected(it))
}
}
} }
Row( Button(onClick = {
horizontalArrangement = Arrangement.SpaceEvenly, fileType.value = DfuBaseService.MIME_TYPE_ZIP
modifier = Modifier.fillMaxWidth() launcher.launch(fileType.value)
) { }) {
Button(onClick = { Text(text = stringResource(id = R.string.dfu_select_zip))
fileType.value = DfuBaseService.MIME_TYPE_ZIP
launcher.launch(fileType.value)
}) {
Text(text = stringResource(id = R.string.dfu_select_zip))
}
Button(onClick = {
fileType.value = DfuBaseService.MIME_TYPE_OCTET_STREAM
launcher.launch(fileType.value)
}) {
Text(text = stringResource(id = R.string.dfu_select_hex))
}
} }
} }

View File

@@ -21,22 +21,25 @@ import no.nordicsemi.dfu.R
internal fun DFUSuccessView(onEvent: (DFUViewEvent) -> Unit) { internal fun DFUSuccessView(onEvent: (DFUViewEvent) -> Unit) {
Column(horizontalAlignment = Alignment.CenterHorizontally) { Column(horizontalAlignment = Alignment.CenterHorizontally) {
ScreenSection { ScreenSection {
val successColor = colorResource(id = no.nordicsemi.android.material.you.R.color.nordicGrass)
Icon( Icon(
painter = painterResource(id = R.drawable.ic_success_circle), painter = painterResource(id = R.drawable.ic_success_circle),
contentDescription = stringResource(id = R.string.dfu_success_icon_description), contentDescription = stringResource(id = R.string.dfu_success_icon_description),
tint = colorResource(id = no.nordicsemi.android.material.you.R.color.nordicGrass) tint = successColor
)
Spacer(modifier = Modifier.size(8.dp))
Text(
text = stringResource(id = R.string.dfu_success),
color = successColor,
style = MaterialTheme.typography.titleLarge
) )
} }
Spacer(modifier = Modifier.size(16.dp)) Spacer(modifier = Modifier.size(16.dp))
Text(
text = stringResource(id = R.string.dfu_success),
color = MaterialTheme.colorScheme.error
)
Spacer(modifier = Modifier.size(16.dp))
Button(onClick = { onEvent(OnPauseButtonClick) }) { Button(onClick = { onEvent(OnPauseButtonClick) }) {
Text(text = stringResource(id = R.string.dfu_done)) Text(text = stringResource(id = R.string.dfu_done))
} }

View File

@@ -27,10 +27,8 @@ import no.nordicsemi.android.theme.view.ScreenSection
import no.nordicsemi.android.theme.view.SectionTitle import no.nordicsemi.android.theme.view.SectionTitle
import no.nordicsemi.dfu.R import no.nordicsemi.dfu.R
import no.nordicsemi.dfu.data.FileReadyState import no.nordicsemi.dfu.data.FileReadyState
import no.nordicsemi.dfu.data.FullHexFile
import no.nordicsemi.dfu.data.ZipFile import no.nordicsemi.dfu.data.ZipFile
import no.nordicsemi.ui.scanner.DiscoveredBluetoothDevice import no.nordicsemi.ui.scanner.DiscoveredBluetoothDevice
import no.nordicsemi.ui.scanner.ui.exhaustive
@Composable @Composable
internal fun DFUSummaryView(state: FileReadyState, onEvent: (DFUViewEvent) -> Unit) { internal fun DFUSummaryView(state: FileReadyState, onEvent: (DFUViewEvent) -> Unit) {
@@ -39,10 +37,7 @@ internal fun DFUSummaryView(state: FileReadyState, onEvent: (DFUViewEvent) -> Un
Spacer(modifier = Modifier.height(16.dp)) Spacer(modifier = Modifier.height(16.dp))
when (state.file) { FileDetailsView(state.file)
is FullHexFile -> FileDetailsView(state.file)
is ZipFile -> FileDetailsView(state.file)
}.exhaustive
Spacer(modifier = Modifier.height(16.dp)) Spacer(modifier = Modifier.height(16.dp))
@@ -73,9 +68,11 @@ internal fun DeviceDetailsView(device: DiscoveredBluetoothDevice) {
Spacer(modifier = Modifier.size(8.dp)) Spacer(modifier = Modifier.size(8.dp))
Column(modifier = Modifier Column(
.fillMaxWidth() modifier = Modifier
.weight(1f)) { .fillMaxWidth()
.weight(1f)
) {
Text( Text(
text = device.displayName(), text = device.displayName(),
style = MaterialTheme.typography.titleMedium style = MaterialTheme.typography.titleMedium
@@ -88,36 +85,26 @@ internal fun DeviceDetailsView(device: DiscoveredBluetoothDevice) {
@Composable @Composable
private fun FileDetailsView(file: ZipFile) { private fun FileDetailsView(file: ZipFile) {
val fileName = file.data.name val fileName = file.name
val fileLength = file.data.size val fileLength = file.size
ScreenSection { ScreenSection {
SectionTitle(icon = Icons.Default.Notifications, title = stringResource(id = R.string.dfu_zip_file_details)) SectionTitle(
icon = Icons.Default.Notifications,
title = stringResource(id = R.string.dfu_zip_file_details)
)
Spacer(modifier = Modifier.size(16.dp)) Spacer(modifier = Modifier.size(16.dp))
Text(text = fileName) Column(
modifier = Modifier.fillMaxWidth(),
horizontalAlignment = Alignment.Start
) {
Text(text = stringResource(id = R.string.dfu_file_name, fileName))
Spacer(modifier = Modifier.size(4.dp)) Spacer(modifier = Modifier.size(4.dp))
Text(text = stringResource(id = R.string.dfu_file_size, fileLength)) Text(text = stringResource(id = R.string.dfu_file_size, fileLength))
} }
}
@Composable
private fun FileDetailsView(file: FullHexFile) {
val fileName = file.data.name
val fileLength = file.data.size
ScreenSection {
SectionTitle(icon = Icons.Default.Notifications, title = stringResource(id = R.string.dfu_hex_file_details))
Spacer(modifier = Modifier.size(16.dp))
Text(text = fileName)
Spacer(modifier = Modifier.size(4.dp))
Text(text = stringResource(id = R.string.dfu_file_size, fileLength))
} }
} }

View File

@@ -5,8 +5,6 @@ import android.net.Uri
internal sealed class DFUViewEvent internal sealed class DFUViewEvent
internal data class OnZipFileSelected(val file: Uri) : DFUViewEvent() internal data class OnZipFileSelected(val file: Uri) : DFUViewEvent()
internal data class OnHexFileSelected(val file: Uri) : DFUViewEvent()
internal data class OnDatFileSelected(val file: Uri) : DFUViewEvent()
internal object OnInstallButtonClick : DFUViewEvent() internal object OnInstallButtonClick : DFUViewEvent()

View File

@@ -9,7 +9,6 @@ import no.nordicsemi.android.service.SelectedBluetoothDeviceHolder
import no.nordicsemi.android.theme.viewmodel.CloseableViewModel import no.nordicsemi.android.theme.viewmodel.CloseableViewModel
import no.nordicsemi.android.utils.exhaustive import no.nordicsemi.android.utils.exhaustive
import no.nordicsemi.dfu.data.Completed import no.nordicsemi.dfu.data.Completed
import no.nordicsemi.dfu.data.DFUFile
import no.nordicsemi.dfu.data.DFUManager import no.nordicsemi.dfu.data.DFUManager
import no.nordicsemi.dfu.data.DFUProgressManager import no.nordicsemi.dfu.data.DFUProgressManager
import no.nordicsemi.dfu.data.DFURepository import no.nordicsemi.dfu.data.DFURepository
@@ -18,10 +17,9 @@ import no.nordicsemi.dfu.data.Error
import no.nordicsemi.dfu.data.FileInstallingState import no.nordicsemi.dfu.data.FileInstallingState
import no.nordicsemi.dfu.data.FileReadyState import no.nordicsemi.dfu.data.FileReadyState
import no.nordicsemi.dfu.data.NoFileSelectedState import no.nordicsemi.dfu.data.NoFileSelectedState
import no.nordicsemi.dfu.data.ZipFile
import no.nordicsemi.dfu.view.DFUViewEvent import no.nordicsemi.dfu.view.DFUViewEvent
import no.nordicsemi.dfu.view.OnDatFileSelected
import no.nordicsemi.dfu.view.OnDisconnectButtonClick import no.nordicsemi.dfu.view.OnDisconnectButtonClick
import no.nordicsemi.dfu.view.OnHexFileSelected
import no.nordicsemi.dfu.view.OnInstallButtonClick import no.nordicsemi.dfu.view.OnInstallButtonClick
import no.nordicsemi.dfu.view.OnPauseButtonClick import no.nordicsemi.dfu.view.OnPauseButtonClick
import no.nordicsemi.dfu.view.OnStopButtonClick import no.nordicsemi.dfu.view.OnStopButtonClick
@@ -55,9 +53,7 @@ internal class DFUViewModel @Inject constructor(
} }
OnPauseButtonClick -> closeScreen() OnPauseButtonClick -> closeScreen()
OnStopButtonClick -> closeScreen() OnStopButtonClick -> closeScreen()
is OnHexFileSelected -> repository.setHexFile(event.file)
is OnZipFileSelected -> repository.setZipFile(event.file) is OnZipFileSelected -> repository.setZipFile(event.file)
is OnDatFileSelected -> repository.setDatFile(event.file)
}.exhaustive }.exhaustive
} }
@@ -67,7 +63,7 @@ internal class DFUViewModel @Inject constructor(
finish() finish()
} }
private fun requireFile(): DFUFile { private fun requireFile(): ZipFile {
return (repository.data.value as FileReadyState).file return (repository.data.value as FileReadyState).file
} }

View File

@@ -17,7 +17,6 @@
<string name="dfu_zip_file_details">Zip file details</string> <string name="dfu_zip_file_details">Zip file details</string>
<string name="dfu_hex_file_details">Hex file details</string> <string name="dfu_hex_file_details">Hex file details</string>
<string name="dfu_file_size">%d bytes</string>
<string name="dfu_file_type_zip">Distribution packet (ZIP)</string> <string name="dfu_file_type_zip">Distribution packet (ZIP)</string>
<string name="dfu_file_type_soft_device">Soft Device</string> <string name="dfu_file_type_soft_device">Soft Device</string>
<string name="dfu_file_type_bootloader">Bootloader</string> <string name="dfu_file_type_bootloader">Bootloader</string>
@@ -52,4 +51,7 @@
<string name="dfu_success">Success!</string> <string name="dfu_success">Success!</string>
<string name="dfu_unknown_error">Unknown error.</string> <string name="dfu_unknown_error">Unknown error.</string>
<string name="dfu_file_name">File name: <b>%s</b></string>
<string name="dfu_file_size">File size: <b>%d</b> bytes</string>
</resources> </resources>