mirror of
https://github.com/aljazceru/Android-nRF-Toolbox.git
synced 2025-12-20 16:04:22 +01:00
Remove hex & bin files from DFU.
This commit is contained in:
@@ -9,15 +9,10 @@ internal data class NoFileSelectedState(
|
||||
) : DFUData()
|
||||
|
||||
internal data class FileReadyState(
|
||||
val file: DFUFile,
|
||||
val file: ZipFile,
|
||||
val device: DiscoveredBluetoothDevice
|
||||
) : DFUData()
|
||||
|
||||
internal data class HexFileLoadedState(
|
||||
val file: PartialHexFile,
|
||||
val isDatFileError: Boolean = false
|
||||
) : DFUData()
|
||||
|
||||
internal data class FileInstallingState(
|
||||
val status: DFUServiceStatus = Idle
|
||||
) : DFUData()
|
||||
|
||||
@@ -2,26 +2,7 @@ package no.nordicsemi.dfu.data
|
||||
|
||||
import android.net.Uri
|
||||
|
||||
sealed class DFUFile {
|
||||
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(
|
||||
data class ZipFile(
|
||||
val uri: Uri,
|
||||
val name: String,
|
||||
val path: String?,
|
||||
|
||||
@@ -15,7 +15,7 @@ class DFUFileManager @Inject constructor(
|
||||
|
||||
private val TAG = "DFU_FILE_MANAGER"
|
||||
|
||||
fun createFile(uri: Uri): FileData? {
|
||||
fun createFile(uri: Uri): ZipFile? {
|
||||
return try {
|
||||
createFromFile(uri)
|
||||
} 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()
|
||||
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)
|
||||
|
||||
return if (data != null && data.moveToNext()) {
|
||||
@@ -53,7 +53,7 @@ class DFUFileManager @Inject constructor(
|
||||
|
||||
data.close()
|
||||
|
||||
FileData(uri, fileName, filePath, fileSize.toLong())
|
||||
ZipFile(uri, fileName, filePath, fileSize.toLong())
|
||||
} else {
|
||||
Log.d(TAG, "Data loaded from ContentResolver is empty.")
|
||||
null
|
||||
|
||||
@@ -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 }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,6 @@ import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import no.nordicsemi.android.dfu.DfuServiceInitiator
|
||||
import no.nordicsemi.android.service.SelectedBluetoothDeviceHolder
|
||||
import no.nordicsemi.dfu.repository.DFUService
|
||||
import no.nordicsemi.ui.scanner.ui.exhaustive
|
||||
import javax.inject.Inject
|
||||
|
||||
class DFUManager @Inject constructor(
|
||||
@@ -14,7 +13,7 @@ class DFUManager @Inject constructor(
|
||||
private val deviceHolder: SelectedBluetoothDeviceHolder
|
||||
) {
|
||||
|
||||
fun install(file: DFUFile) {
|
||||
fun install(file: ZipFile) {
|
||||
val device = deviceHolder.device!!
|
||||
|
||||
val starter = DfuServiceInitiator(device.address())
|
||||
@@ -26,12 +25,7 @@ class DFUManager @Inject constructor(
|
||||
.setPrepareDataObjectDelay(400)
|
||||
.setUnsafeExperimentalButtonlessServiceInSecureDfuEnabled(true)
|
||||
|
||||
when (file) {
|
||||
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.setZip(file.uri, file.path)
|
||||
starter.start(context, DFUService::class.java)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,24 +20,10 @@ internal class DFURepository @Inject constructor(
|
||||
fun setZipFile(file: Uri) {
|
||||
val currentState = _data.value as NoFileSelectedState
|
||||
_data.value = fileManger.createFile(file)?.let {
|
||||
FileReadyState(ZipFile(it), requireNotNull(deviceHolder.device))
|
||||
FileReadyState(it, requireNotNull(deviceHolder.device))
|
||||
} ?: 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() {
|
||||
_data.value = UploadSuccessState
|
||||
}
|
||||
|
||||
@@ -14,7 +14,6 @@ internal fun DFUContentView(state: DFUData, onEvent: (DFUViewEvent) -> Unit) {
|
||||
when (state) {
|
||||
is NoFileSelectedState -> DFUSelectMainFileView(state, onEvent)
|
||||
is FileReadyState -> DFUSummaryView(state, onEvent)
|
||||
is HexFileLoadedState -> DFUSelectDatFileView(state, onEvent)
|
||||
UploadSuccessState -> DFUSuccessView(onEvent)
|
||||
is UploadFailureState -> DFUErrorView(state, onEvent)
|
||||
is FileInstallingState -> DFUInstallingView(state, onEvent)
|
||||
|
||||
@@ -21,18 +21,22 @@ import no.nordicsemi.dfu.data.UploadFailureState
|
||||
internal fun DFUErrorView(state: UploadFailureState, onEvent: (DFUViewEvent) -> Unit) {
|
||||
Column(horizontalAlignment = Alignment.CenterHorizontally) {
|
||||
ScreenSection {
|
||||
val errorColor = MaterialTheme.colorScheme.error
|
||||
|
||||
Icon(
|
||||
painter = painterResource(id = R.drawable.ic_fail_circle),
|
||||
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)
|
||||
Text(
|
||||
text = error,
|
||||
color = MaterialTheme.colorScheme.error
|
||||
color = errorColor,
|
||||
style = MaterialTheme.typography.titleLarge
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.size(16.dp))
|
||||
|
||||
@@ -1,19 +1,13 @@
|
||||
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.height
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.material3.Button
|
||||
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.material.you.CircularProgressIndicator
|
||||
import no.nordicsemi.android.theme.view.ScreenSection
|
||||
import no.nordicsemi.dfu.R
|
||||
import no.nordicsemi.dfu.data.FileInstallingState
|
||||
|
||||
@Composable
|
||||
@@ -24,19 +18,5 @@ internal fun DFUInstallingView(state: FileInstallingState, onEvent: (DFUViewEven
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
|
||||
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))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,11 +2,7 @@ package no.nordicsemi.dfu.view
|
||||
|
||||
import androidx.activity.compose.rememberLauncherForActivityResult
|
||||
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.fillMaxWidth
|
||||
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
|
||||
@@ -62,31 +58,13 @@ private fun ButtonsRow(onEvent: (DFUViewEvent) -> Unit) {
|
||||
val fileType = rememberSaveable { mutableStateOf(DfuBaseService.MIME_TYPE_ZIP) }
|
||||
|
||||
val launcher = rememberLauncherForActivityResult(ActivityResultContracts.GetContent()) { uri ->
|
||||
uri?.let {
|
||||
if (fileType.value == DfuBaseService.MIME_TYPE_ZIP) {
|
||||
onEvent(OnZipFileSelected(it))
|
||||
} else {
|
||||
onEvent(OnHexFileSelected(it))
|
||||
}
|
||||
}
|
||||
uri?.let { onEvent(OnZipFileSelected(it)) }
|
||||
}
|
||||
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.SpaceEvenly,
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
) {
|
||||
Button(onClick = {
|
||||
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))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,19 +21,22 @@ import no.nordicsemi.dfu.R
|
||||
internal fun DFUSuccessView(onEvent: (DFUViewEvent) -> Unit) {
|
||||
Column(horizontalAlignment = Alignment.CenterHorizontally) {
|
||||
ScreenSection {
|
||||
val successColor = colorResource(id = no.nordicsemi.android.material.you.R.color.nordicGrass)
|
||||
|
||||
Icon(
|
||||
painter = painterResource(id = R.drawable.ic_success_circle),
|
||||
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(16.dp))
|
||||
Spacer(modifier = Modifier.size(8.dp))
|
||||
|
||||
Text(
|
||||
text = stringResource(id = R.string.dfu_success),
|
||||
color = MaterialTheme.colorScheme.error
|
||||
color = successColor,
|
||||
style = MaterialTheme.typography.titleLarge
|
||||
)
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.size(16.dp))
|
||||
|
||||
|
||||
@@ -27,10 +27,8 @@ import no.nordicsemi.android.theme.view.ScreenSection
|
||||
import no.nordicsemi.android.theme.view.SectionTitle
|
||||
import no.nordicsemi.dfu.R
|
||||
import no.nordicsemi.dfu.data.FileReadyState
|
||||
import no.nordicsemi.dfu.data.FullHexFile
|
||||
import no.nordicsemi.dfu.data.ZipFile
|
||||
import no.nordicsemi.ui.scanner.DiscoveredBluetoothDevice
|
||||
import no.nordicsemi.ui.scanner.ui.exhaustive
|
||||
|
||||
@Composable
|
||||
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))
|
||||
|
||||
when (state.file) {
|
||||
is FullHexFile -> FileDetailsView(state.file)
|
||||
is ZipFile -> FileDetailsView(state.file)
|
||||
}.exhaustive
|
||||
FileDetailsView(state.file)
|
||||
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
|
||||
@@ -73,9 +68,11 @@ internal fun DeviceDetailsView(device: DiscoveredBluetoothDevice) {
|
||||
|
||||
Spacer(modifier = Modifier.size(8.dp))
|
||||
|
||||
Column(modifier = Modifier
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.weight(1f)) {
|
||||
.weight(1f)
|
||||
) {
|
||||
Text(
|
||||
text = device.displayName(),
|
||||
style = MaterialTheme.typography.titleMedium
|
||||
@@ -88,36 +85,26 @@ internal fun DeviceDetailsView(device: DiscoveredBluetoothDevice) {
|
||||
|
||||
@Composable
|
||||
private fun FileDetailsView(file: ZipFile) {
|
||||
val fileName = file.data.name
|
||||
val fileLength = file.data.size
|
||||
val fileName = file.name
|
||||
val fileLength = file.size
|
||||
|
||||
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))
|
||||
|
||||
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))
|
||||
|
||||
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))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,8 +5,6 @@ import android.net.Uri
|
||||
internal sealed class 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()
|
||||
|
||||
|
||||
@@ -9,7 +9,6 @@ import no.nordicsemi.android.service.SelectedBluetoothDeviceHolder
|
||||
import no.nordicsemi.android.theme.viewmodel.CloseableViewModel
|
||||
import no.nordicsemi.android.utils.exhaustive
|
||||
import no.nordicsemi.dfu.data.Completed
|
||||
import no.nordicsemi.dfu.data.DFUFile
|
||||
import no.nordicsemi.dfu.data.DFUManager
|
||||
import no.nordicsemi.dfu.data.DFUProgressManager
|
||||
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.FileReadyState
|
||||
import no.nordicsemi.dfu.data.NoFileSelectedState
|
||||
import no.nordicsemi.dfu.data.ZipFile
|
||||
import no.nordicsemi.dfu.view.DFUViewEvent
|
||||
import no.nordicsemi.dfu.view.OnDatFileSelected
|
||||
import no.nordicsemi.dfu.view.OnDisconnectButtonClick
|
||||
import no.nordicsemi.dfu.view.OnHexFileSelected
|
||||
import no.nordicsemi.dfu.view.OnInstallButtonClick
|
||||
import no.nordicsemi.dfu.view.OnPauseButtonClick
|
||||
import no.nordicsemi.dfu.view.OnStopButtonClick
|
||||
@@ -55,9 +53,7 @@ internal class DFUViewModel @Inject constructor(
|
||||
}
|
||||
OnPauseButtonClick -> closeScreen()
|
||||
OnStopButtonClick -> closeScreen()
|
||||
is OnHexFileSelected -> repository.setHexFile(event.file)
|
||||
is OnZipFileSelected -> repository.setZipFile(event.file)
|
||||
is OnDatFileSelected -> repository.setDatFile(event.file)
|
||||
}.exhaustive
|
||||
}
|
||||
|
||||
@@ -67,7 +63,7 @@ internal class DFUViewModel @Inject constructor(
|
||||
finish()
|
||||
}
|
||||
|
||||
private fun requireFile(): DFUFile {
|
||||
private fun requireFile(): ZipFile {
|
||||
return (repository.data.value as FileReadyState).file
|
||||
}
|
||||
|
||||
|
||||
@@ -17,7 +17,6 @@
|
||||
<string name="dfu_zip_file_details">Zip 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_soft_device">Soft Device</string>
|
||||
<string name="dfu_file_type_bootloader">Bootloader</string>
|
||||
@@ -52,4 +51,7 @@
|
||||
|
||||
<string name="dfu_success">Success!</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>
|
||||
|
||||
Reference in New Issue
Block a user