diff --git a/lib/analytics/src/main/java/no/nordicsemi/android/dfu/analytics/Events.kt b/lib/analytics/src/main/java/no/nordicsemi/android/dfu/analytics/Events.kt index d606f068..8cb6e4ff 100644 --- a/lib/analytics/src/main/java/no/nordicsemi/android/dfu/analytics/Events.kt +++ b/lib/analytics/src/main/java/no/nordicsemi/android/dfu/analytics/Events.kt @@ -110,6 +110,12 @@ class PrepareDataObjectDelaySettingsEvent(private val delay: Int) : DFUSettingsC override fun createBundle() = bundleOf(FirebaseParam.VALUE to delay) } +class ExecuteInitDelaySettingsEvent(private val delay: Int) : DFUSettingsChangeEvent { + override val eventName: String = "EXECUTE_INIT_DELAY_EVENT" + + override fun createBundle() = bundleOf(FirebaseParam.VALUE to delay) +} + class RebootTimeSettingsEvent(private val rebootTime: Int) : DFUSettingsChangeEvent { override val eventName: String = "REBOOT_TIME_EVENT" diff --git a/lib/dfu/src/main/java/no/nordicsemi/android/dfu/DfuBaseService.java b/lib/dfu/src/main/java/no/nordicsemi/android/dfu/DfuBaseService.java index d2fc0c4f..8dad9932 100644 --- a/lib/dfu/src/main/java/no/nordicsemi/android/dfu/DfuBaseService.java +++ b/lib/dfu/src/main/java/no/nordicsemi/android/dfu/DfuBaseService.java @@ -278,6 +278,11 @@ public abstract class DfuBaseService extends IntentService implements DfuProgres * in milliseconds. This defaults to 0 for backwards compatibility reason. */ public static final String EXTRA_DATA_OBJECT_DELAY = "no.nordicsemi.android.dfu.extra.EXTRA_DATA_OBJECT_DELAY"; + /** + * The duration of a delay that will be added after sending the Init Packet and before + * executing it, in milliseconds. This defaults to 0 for backwards compatibility reason. + */ + public static final String EXTRA_INIT_EXECUTE_DELAY = "no.nordicsemi.android.dfu.extra.EXTRA_INIT_EXECUTE_DELAY"; /** * This property must contain a boolean value. *

diff --git a/lib/dfu/src/main/java/no/nordicsemi/android/dfu/DfuServiceInitiator.java b/lib/dfu/src/main/java/no/nordicsemi/android/dfu/DfuServiceInitiator.java index 7c20c360..48d4c3bc 100644 --- a/lib/dfu/src/main/java/no/nordicsemi/android/dfu/DfuServiceInitiator.java +++ b/lib/dfu/src/main/java/no/nordicsemi/android/dfu/DfuServiceInitiator.java @@ -108,6 +108,7 @@ public final class DfuServiceInitiator { private int numberOfRetries = 0; // 0 to be backwards compatible private int mbrSize = DEFAULT_MBR_SIZE; private long dataObjectDelay = 0; // initially disabled + private long executeInitDelay = 0; // initially disabled private long rebootTime = 0; // ms private long scanTimeout = DEFAULT_SCAN_TIMEOUT; // ms @@ -245,6 +246,19 @@ public DfuServiceInitiator setPrepareDataObjectDelay(final long delay) { return this; } + /** + * This method sets the duration of a delay, that the service will wait after sending the + * init packet and before executing it. The delay will be done after the init packet is sent + * and before sending the execute command. The default value is 0, which disables this feature. + * + * @param delay the delay that the service will wait before executing the init packet in milliseconds. + * @return the builder + */ + public DfuServiceInitiator setExecuteInitDelay(final long delay) { + this.executeInitDelay = delay; + return this; + } + /** * Enables or disables the Packet Receipt Notification (PRN) procedure. *

@@ -870,6 +884,7 @@ public DfuServiceController start(@NonNull final Context context, @NonNull final intent.putExtra(DfuBaseService.EXTRA_MAX_DFU_ATTEMPTS, numberOfRetries); intent.putExtra(DfuBaseService.EXTRA_MBR_SIZE, mbrSize); intent.putExtra(DfuBaseService.EXTRA_DATA_OBJECT_DELAY, dataObjectDelay); + intent.putExtra(DfuBaseService.EXTRA_INIT_EXECUTE_DELAY, executeInitDelay); intent.putExtra(DfuBaseService.EXTRA_SCAN_TIMEOUT, scanTimeout); intent.putExtra(DfuBaseService.EXTRA_SCAN_DELAY, rebootTime); if (mtu > 0) diff --git a/lib/dfu/src/main/java/no/nordicsemi/android/dfu/SecureDfuImpl.java b/lib/dfu/src/main/java/no/nordicsemi/android/dfu/SecureDfuImpl.java index 8c5ea8eb..26c9b73e 100644 --- a/lib/dfu/src/main/java/no/nordicsemi/android/dfu/SecureDfuImpl.java +++ b/lib/dfu/src/main/java/no/nordicsemi/android/dfu/SecureDfuImpl.java @@ -83,6 +83,7 @@ class SecureDfuImpl extends BaseCustomDfuImpl { private BluetoothGattCharacteristic mPacketCharacteristic; private long prepareObjectDelay; + private long executeInitDelay; private final SecureBluetoothCallback mBluetoothCallback = new SecureBluetoothCallback(); @@ -223,6 +224,7 @@ public void performDfu(@NonNull final Intent intent) } prepareObjectDelay = intent.getLongExtra(DfuBaseService.EXTRA_DATA_OBJECT_DELAY, 0); + executeInitDelay = intent.getLongExtra(DfuBaseService.EXTRA_INIT_EXECUTE_DELAY, 0); try { // Enable notifications @@ -472,6 +474,8 @@ private void sendInitPacket(@NonNull final BluetoothGatt gatt, final boolean all } // Execute Init packet. It's better to execute it twice than not execute at all... + if (executeInitDelay > 0) + mService.waitFor(executeInitDelay); logi("Executing init packet (Op Code = 4)"); writeExecute(); mService.sendLogBroadcast(DfuBaseService.LOG_LEVEL_APPLICATION, "Command object executed"); diff --git a/lib/settings/src/main/java/no/nordicsemi/android/dfu/settings/domain/DFUSettings.kt b/lib/settings/src/main/java/no/nordicsemi/android/dfu/settings/domain/DFUSettings.kt index 3d2e055a..2454c027 100644 --- a/lib/settings/src/main/java/no/nordicsemi/android/dfu/settings/domain/DFUSettings.kt +++ b/lib/settings/src/main/java/no/nordicsemi/android/dfu/settings/domain/DFUSettings.kt @@ -44,6 +44,7 @@ data class DFUSettings( val externalMcuDfu: Boolean = false, val disableResume: Boolean = false, val prepareDataObjectDelay: Int = 0, // ms + val executeInitDelay: Int = 0, // ms val rebootTime: Int = 0, // ms val scanTimeout: Int = 6_000, // ms val mtuRequestEnabled: Boolean = true, diff --git a/lib/settings/src/main/java/no/nordicsemi/android/dfu/settings/repository/SettingsDataSource.kt b/lib/settings/src/main/java/no/nordicsemi/android/dfu/settings/repository/SettingsDataSource.kt index 6445211d..8a91b6b8 100644 --- a/lib/settings/src/main/java/no/nordicsemi/android/dfu/settings/repository/SettingsDataSource.kt +++ b/lib/settings/src/main/java/no/nordicsemi/android/dfu/settings/repository/SettingsDataSource.kt @@ -53,6 +53,7 @@ private val DISABLE_RESUME = booleanPreferencesKey("disable_resume") private val FORCE_SCANNING_ADDRESS = booleanPreferencesKey("force_scanning_address") private val NUMBER_OF_POCKETS_KEY = intPreferencesKey("number_of_pockets") private val PREPARE_OBJECT_DELAY_KEY = intPreferencesKey("prepare_data_object_delay") +private val EXECUTE_INIT_DELAY_KEY = intPreferencesKey("execute_init_delay") private val REBOOT_TIME_KEY = intPreferencesKey("reboot_time") private val SCAN_TIMEOUT_KEY = intPreferencesKey("scan_timeout") private val MTU = booleanPreferencesKey("requestMtu") @@ -75,6 +76,7 @@ class SettingsDataSource @Inject constructor( context.dataStore.edit { it[PACKETS_RECEIPT_NOTIFICATION_KEY] = settings.packetsReceiptNotification it[PREPARE_OBJECT_DELAY_KEY] = settings.prepareDataObjectDelay + it[EXECUTE_INIT_DELAY_KEY] = settings.executeInitDelay it[REBOOT_TIME_KEY] = settings.rebootTime it[SCAN_TIMEOUT_KEY] = settings.scanTimeout it[NUMBER_OF_POCKETS_KEY] = settings.numberOfPackets @@ -95,6 +97,7 @@ class SettingsDataSource @Inject constructor( this[EXTERNAL_MCU_KEY] ?: false, this[DISABLE_RESUME] ?: false, this[PREPARE_OBJECT_DELAY_KEY] ?: 400, + this[EXECUTE_INIT_DELAY_KEY] ?: 0, this[REBOOT_TIME_KEY] ?: 0, this[SCAN_TIMEOUT_KEY] ?: 2_000, this[MTU] ?: true, diff --git a/profile/main/src/main/java/no/nordicsemi/android/dfu/profile/main/data/DFUManager.kt b/profile/main/src/main/java/no/nordicsemi/android/dfu/profile/main/data/DFUManager.kt index a88f856e..1921dda7 100644 --- a/profile/main/src/main/java/no/nordicsemi/android/dfu/profile/main/data/DFUManager.kt +++ b/profile/main/src/main/java/no/nordicsemi/android/dfu/profile/main/data/DFUManager.kt @@ -76,6 +76,7 @@ internal class DFUManager @Inject constructor( setForceScanningForNewAddressInLegacyDfu(settings.forceScanningInLegacyDfu) setPrepareDataObjectDelay(settings.prepareDataObjectDelay.toLong()) + setExecuteInitDelay(settings.executeInitDelay.toLong()) setRebootTime(settings.rebootTime.toLong()) setScanTimeout(settings.scanTimeout.toLong()) setUnsafeExperimentalButtonlessServiceInSecureDfuEnabled(true) diff --git a/profile/settings/src/main/java/no/nordicsemi/android/dfu/profile/settings/view/SettingsScreen.kt b/profile/settings/src/main/java/no/nordicsemi/android/dfu/profile/settings/view/SettingsScreen.kt index 77264022..e8807b63 100644 --- a/profile/settings/src/main/java/no/nordicsemi/android/dfu/profile/settings/view/SettingsScreen.kt +++ b/profile/settings/src/main/java/no/nordicsemi/android/dfu/profile/settings/view/SettingsScreen.kt @@ -157,6 +157,15 @@ internal fun SettingsScreen( onChange = { onEvent(OnPrepareDataObjectDelayChange(it)) } ) + SettingsTimeSlider( + text = stringResource(id = R.string.dfu_settings_execute_init_delay), + description = stringResource(id = R.string.dfu_settings_execute_init_delay_info), + value = state.executeInitDelay, + valueRange = 0..1000, + stepInMilliseconds = 100, // 0.1 seconds + onChange = { onEvent(OnExecuteInitDelayChange(it)) } + ) + Headline(stringResource(id = R.string.dfu_settings_headline_legacy_dfu)) SettingsSwitch( diff --git a/profile/settings/src/main/java/no/nordicsemi/android/dfu/profile/settings/view/SettingsScreenViewEvent.kt b/profile/settings/src/main/java/no/nordicsemi/android/dfu/profile/settings/view/SettingsScreenViewEvent.kt index 742e3b51..2267fb95 100644 --- a/profile/settings/src/main/java/no/nordicsemi/android/dfu/profile/settings/view/SettingsScreenViewEvent.kt +++ b/profile/settings/src/main/java/no/nordicsemi/android/dfu/profile/settings/view/SettingsScreenViewEvent.kt @@ -39,6 +39,8 @@ internal data class OnNumberOfPocketsChange(val numberOfPockets: Int) : Settings internal data class OnPrepareDataObjectDelayChange(val delay: Int) : SettingsScreenViewEvent +internal data class OnExecuteInitDelayChange(val delay: Int) : SettingsScreenViewEvent + internal data class OnRebootTimeChange(val time: Int) : SettingsScreenViewEvent internal data class OnScanTimeoutChange(val timeout: Int) : SettingsScreenViewEvent diff --git a/profile/settings/src/main/java/no/nordicsemi/android/dfu/profile/settings/viewmodel/SettingsViewModel.kt b/profile/settings/src/main/java/no/nordicsemi/android/dfu/profile/settings/viewmodel/SettingsViewModel.kt index d49663aa..bbc68fa6 100644 --- a/profile/settings/src/main/java/no/nordicsemi/android/dfu/profile/settings/viewmodel/SettingsViewModel.kt +++ b/profile/settings/src/main/java/no/nordicsemi/android/dfu/profile/settings/viewmodel/SettingsViewModel.kt @@ -71,6 +71,7 @@ internal class SettingsViewModel @Inject constructor( OnForceScanningAddressesSwitchClick -> onForceScanningAddressesSwitchClick() is OnNumberOfPocketsChange -> onNumberOfPocketsChange(event.numberOfPockets) is OnPrepareDataObjectDelayChange -> onPrepareDataObjectDelayChange(event.delay) + is OnExecuteInitDelayChange -> onExecuteInitDelayChange(event.delay) is OnRebootTimeChange -> onRebootTimeChange(event.time) is OnScanTimeoutChange -> onScanTimeoutChange(event.timeout) } @@ -144,6 +145,14 @@ internal class SettingsViewModel @Inject constructor( analytics.logEvent(PrepareDataObjectDelaySettingsEvent(newSettings.prepareDataObjectDelay)) } + private fun onExecuteInitDelayChange(delay: Int) { + val newSettings = state.value.copy(executeInitDelay = delay) + viewModelScope.launch { + repository.storeSettings(newSettings) + } + analytics.logEvent(ExecuteInitDelaySettingsEvent(newSettings.executeInitDelay)) + } + private fun onRebootTimeChange(rebootTime: Int) { val newSettings = state.value.copy(rebootTime = rebootTime) viewModelScope.launch { diff --git a/profile/settings/src/main/res/values/strings.xml b/profile/settings/src/main/res/values/strings.xml index bfcf6e98..59bc371b 100644 --- a/profile/settings/src/main/res/values/strings.xml +++ b/profile/settings/src/main/res/values/strings.xml @@ -41,6 +41,8 @@ When the resume feature is disabled the app will not try to resume previously interrupted update. Prepare object delay The time required by the DFU bootloader to save the data object in flash memory. Shorter delays will increase upload speed, but may cause issues on some devices. + Execute init delay + The time the service will wait after sending the init packet and before executing it. Reboot time The time the device needs to reboot into the DFU bootloader mode. The app will delay scanning for the bootloader. Scan timeout