From 1e5b7a9824e902e144dc1e90aa0cf6b5d8bcb775 Mon Sep 17 00:00:00 2001 From: Aleksander Nowakowski Date: Mon, 18 Feb 2019 15:13:00 +0100 Subject: [PATCH 01/13] Increase number of reconnection attempts --- .../java/no/nordicsemi/android/dfu/DfuBaseService.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/dfu/src/main/java/no/nordicsemi/android/dfu/DfuBaseService.java b/dfu/src/main/java/no/nordicsemi/android/dfu/DfuBaseService.java index 824cd61..a965964 100644 --- a/dfu/src/main/java/no/nordicsemi/android/dfu/DfuBaseService.java +++ b/dfu/src/main/java/no/nordicsemi/android/dfu/DfuBaseService.java @@ -1182,8 +1182,9 @@ public abstract class DfuBaseService extends IntentService implements DfuProgres sendLogBroadcast(LOG_LEVEL_ERROR, String.format(Locale.US, "Connection failed (0x%02X): %s", error, GattError.parse(error))); } // Connection usually fails due to a 133 error (device unreachable, or.. something else went wrong). - // Usually trying the same for the second time works. - if (intent.getIntExtra(EXTRA_ATTEMPT, 0) == 0) { + // Usually trying the same for the second time works. Let's try 2 times. + final int attempt = intent.getIntExtra(EXTRA_ATTEMPT, 0); + if (attempt < 2) { sendLogBroadcast(LOG_LEVEL_WARNING, "Retrying..."); if (mConnectionState != STATE_DISCONNECTED) { @@ -1197,7 +1198,7 @@ public abstract class DfuBaseService extends IntentService implements DfuProgres logi("Restarting the service"); final Intent newIntent = new Intent(); newIntent.fillIn(intent, Intent.FILL_IN_COMPONENT | Intent.FILL_IN_PACKAGE); - newIntent.putExtra(EXTRA_ATTEMPT, 1); + newIntent.putExtra(EXTRA_ATTEMPT, attempt + 1); startService(newIntent); return; } From c63c06ee797a89c6abac38dc72c03bac22a5e78a Mon Sep 17 00:00:00 2001 From: Aleksander Nowakowski Date: Tue, 19 Feb 2019 15:22:14 +0100 Subject: [PATCH 02/13] Increase number of reconnection attempts --- .../android/dfu/DfuBaseService.java | 43 +++++++++++-------- 1 file changed, 24 insertions(+), 19 deletions(-) diff --git a/dfu/src/main/java/no/nordicsemi/android/dfu/DfuBaseService.java b/dfu/src/main/java/no/nordicsemi/android/dfu/DfuBaseService.java index a965964..9c2e259 100644 --- a/dfu/src/main/java/no/nordicsemi/android/dfu/DfuBaseService.java +++ b/dfu/src/main/java/no/nordicsemi/android/dfu/DfuBaseService.java @@ -117,8 +117,11 @@ public abstract class DfuBaseService extends IntentService implements DfuProgres *

Read more here: https://developer.android.com/about/versions/oreo/background.html

*/ public static final String EXTRA_FOREGROUND_SERVICE = "no.nordicsemi.android.dfu.extra.EXTRA_FOREGROUND_SERVICE"; - /** An extra private field indicating which attempt is being performed. In case of error 133 the service will retry to connect one more time. */ - private static final String EXTRA_ATTEMPT = "no.nordicsemi.android.dfu.extra.EXTRA_ATTEMPT"; + /** + * An extra private field indicating which reconnection attempt is being performed. + * In case of error 133 the service will retry to connect 2 more times. + */ + private static final String EXTRA_RECONNECTION_ATTEMPT = "no.nordicsemi.android.dfu.extra.EXTRA_RECONNECTION_ATTEMPT"; /** *

* If the new firmware (application) does not share the bond information with the old one, the bond information is lost. Set this flag to true @@ -1152,7 +1155,9 @@ public abstract class DfuBaseService extends IntentService implements DfuProgres sendLogBroadcast(LOG_LEVEL_VERBOSE, "Connecting to DFU target..."); mProgressInfo.setProgress(PROGRESS_CONNECTING); + final long before = SystemClock.elapsedRealtime(); final BluetoothGatt gatt = connect(deviceAddress); + final long after = SystemClock.elapsedRealtime(); // Are we connected? if (gatt == null) { loge("Bluetooth adapter disabled"); @@ -1160,22 +1165,17 @@ public abstract class DfuBaseService extends IntentService implements DfuProgres report(ERROR_BLUETOOTH_DISABLED); return; } - if (mConnectionState == STATE_DISCONNECTED) { - if (mError == (ERROR_CONNECTION_STATE_MASK | 133)) { - loge("Device not reachable. Check if the device with address " + deviceAddress + " is in range, is advertising and is connectable"); - sendLogBroadcast(LOG_LEVEL_ERROR, "Error 133: Connection timeout"); - } else { - loge("Device got disconnected before service discovery finished"); - sendLogBroadcast(LOG_LEVEL_ERROR, "Disconnected"); - } - terminateConnection(gatt, ERROR_DEVICE_DISCONNECTED); - return; - } if (mError > 0) { // error occurred if ((mError & ERROR_CONNECTION_STATE_MASK) > 0) { final int error = mError & ~ERROR_CONNECTION_STATE_MASK; - loge("An error occurred while connecting to the device:" + error); - sendLogBroadcast(LOG_LEVEL_ERROR, String.format(Locale.US, "Connection failed (0x%02X): %s", error, GattError.parseConnectionError(error))); + final boolean timeout = error == 133 && after > before + 25000; // timeout is 30 sec + if (timeout) { + loge("Device not reachable. Check if the device with address " + deviceAddress + " is in range, is advertising and is connectable"); + sendLogBroadcast(LOG_LEVEL_ERROR, "Error 133: Connection timeout"); + } else { + loge("An error occurred while connecting to the device:" + error); + sendLogBroadcast(LOG_LEVEL_ERROR, String.format(Locale.US, "Connection failed (0x%02X): %s", error, GattError.parseConnectionError(error))); + } } else { final int error = mError & ~ERROR_CONNECTION_MASK; loge("An error occurred during discovering services:" + error); @@ -1183,7 +1183,7 @@ public abstract class DfuBaseService extends IntentService implements DfuProgres } // Connection usually fails due to a 133 error (device unreachable, or.. something else went wrong). // Usually trying the same for the second time works. Let's try 2 times. - final int attempt = intent.getIntExtra(EXTRA_ATTEMPT, 0); + final int attempt = intent.getIntExtra(EXTRA_RECONNECTION_ATTEMPT, 0); if (attempt < 2) { sendLogBroadcast(LOG_LEVEL_WARNING, "Retrying..."); @@ -1198,13 +1198,18 @@ public abstract class DfuBaseService extends IntentService implements DfuProgres logi("Restarting the service"); final Intent newIntent = new Intent(); newIntent.fillIn(intent, Intent.FILL_IN_COMPONENT | Intent.FILL_IN_PACKAGE); - newIntent.putExtra(EXTRA_ATTEMPT, attempt + 1); + newIntent.putExtra(EXTRA_RECONNECTION_ATTEMPT, attempt + 1); startService(newIntent); return; } terminateConnection(gatt, mError); return; } + if (mConnectionState == STATE_DISCONNECTED) { + sendLogBroadcast(LOG_LEVEL_ERROR, "Disconnected"); + terminateConnection(gatt, ERROR_DEVICE_DISCONNECTED); + return; + } if (mAborted) { logw("Upload aborted"); sendLogBroadcast(LOG_LEVEL_WARNING, "Upload aborted"); @@ -1214,8 +1219,8 @@ public abstract class DfuBaseService extends IntentService implements DfuProgres } sendLogBroadcast(LOG_LEVEL_INFO, "Services discovered"); - // Reset the attempt counter - intent.putExtra(EXTRA_ATTEMPT, 0); + // Reset the reconnection attempt counter + intent.putExtra(EXTRA_RECONNECTION_ATTEMPT, 0); DfuService dfuService = null; try { From e4c3f1d9239e0f33a53e25ac36536368c5516da7 Mon Sep 17 00:00:00 2001 From: Aleksander Nowakowski Date: Wed, 20 Feb 2019 09:59:09 +0100 Subject: [PATCH 03/13] Comments reformatted --- .../android/dfu/DfuBaseService.java | 377 +++++++++++------- 1 file changed, 238 insertions(+), 139 deletions(-) diff --git a/dfu/src/main/java/no/nordicsemi/android/dfu/DfuBaseService.java b/dfu/src/main/java/no/nordicsemi/android/dfu/DfuBaseService.java index 9c2e259..67b3d5b 100644 --- a/dfu/src/main/java/no/nordicsemi/android/dfu/DfuBaseService.java +++ b/dfu/src/main/java/no/nordicsemi/android/dfu/DfuBaseService.java @@ -107,14 +107,17 @@ public abstract class DfuBaseService extends IntentService implements DfuProgres */ public static final String EXTRA_DEVICE_NAME = "no.nordicsemi.android.dfu.extra.EXTRA_DEVICE_NAME"; /** - * A boolean indicating whether to disable the progress notification in the status bar. Defaults to false. + * A boolean indicating whether to disable the progress notification in the status bar. + * Defaults to false. */ public static final String EXTRA_DISABLE_NOTIFICATION = "no.nordicsemi.android.dfu.extra.EXTRA_DISABLE_NOTIFICATION"; /** - * A boolean indicating whether the DFU service should be set as a foreground service. It is recommended to have it - * as a background service at least on Android Oreo or newer as the background service will be killed by the system - * few moments after the user closed the foreground app. - *

Read more here: https://developer.android.com/about/versions/oreo/background.html

+ * A boolean indicating whether the DFU service should be set as a foreground service. + * It is recommended to have it as a background service at least on Android Oreo or newer as + * the background service will be killed by the system few moments after the user closed the + * foreground app. + *

+ * Read more here: https://developer.android.com/about/versions/oreo/background.html */ public static final String EXTRA_FOREGROUND_SERVICE = "no.nordicsemi.android.dfu.extra.EXTRA_FOREGROUND_SERVICE"; /** @@ -123,47 +126,77 @@ public abstract class DfuBaseService extends IntentService implements DfuProgres */ private static final String EXTRA_RECONNECTION_ATTEMPT = "no.nordicsemi.android.dfu.extra.EXTRA_RECONNECTION_ATTEMPT"; /** + * An extra private field indicating which DFU attempt is being performed. + * If the target device will disconnect for some unknown reason during DFU, the service will + * retry to connect and continue. In case of Legacy DFU it will reconnect and restart process. + */ + /* package */ static final String EXTRA_DFU_ATTEMPT = "no.nordicsemi.android.dfu.extra.EXTRA_DFU_ATTEMPT"; + /** + * Maximum number of DFU attempts. Default value is 0. + */ + public static final String EXTRA_MAX_DFU_ATTEMPTS = "no.nordicsemi.android.dfu.extra.EXTRA_MAX_DFU_ATTEMPTS"; + /** + * If the new firmware (application) does not share the bond information with the old one, + * the bond information is lost. Set this flag to true to make the service create + * new bond with the new application when the upload is done (and remove the old one). + * When set to false (default), the DFU service assumes that the LTK is shared + * between them. Note: currently it is not possible to remove the old bond without creating + * a new one so if your old application supported bonding while the new one does not you have + * to modify the source code yourself. *

- * If the new firmware (application) does not share the bond information with the old one, the bond information is lost. Set this flag to true - * to make the service create new bond with the new application when the upload is done (and remove the old one). When set to false (default), - * the DFU service assumes that the LTK is shared between them. Note: currently it is not possible to remove the old bond without creating a new one so if - * your old application supported bonding while the new one does not you have to modify the source code yourself. - *

+ * In case of updating the soft device the application is always removed together with the + * bond information. *

- * In case of updating the soft device the application is always removed together with the bond information. - *

+ * Search for occurrences of EXTRA_RESTORE_BOND in this file to check the implementation and + * get more details. *

- * Search for occurrences of EXTRA_RESTORE_BOND in this file to check the implementation and get more details. - *

- *

This flag is ignored when Secure DFU Buttonless Service is used. It will keep or will not restore the bond depending on the Buttonless service type.

+ * This flag is ignored when Secure DFU Buttonless Service is used. + * It will keep or will not restore the bond depending on the Buttonless service type. */ public static final String EXTRA_RESTORE_BOND = "no.nordicsemi.android.dfu.extra.EXTRA_RESTORE_BOND"; /** - *

This flag indicated whether the bond information should be kept or removed after an upgrade of the Application. - * If an application is being updated on a bonded device with the DFU Bootloader that has been configured to preserve the bond information for the new application, - * set it to true.

- * - *

By default the Legacy DFU Bootloader clears the whole application's memory. It may be however configured in the \Nordic\nrf51\components\libraries\bootloader_dfu\dfu_types.h - * file (sdk 11, line 76: #define DFU_APP_DATA_RESERVED 0x0000) to preserve some pages. The BLE_APP_HRM_DFU sample app stores the LTK and System Attributes in the first - * two pages, so in order to preserve the bond information this value should be changed to 0x0800 or more. For Secure DFU this value is by default set to 3 pages. - * When those data are preserved, the new Application will notify the app with the Service Changed indication when launched for the first time. Otherwise this - * service will remove the bond information from the phone and force to refresh the device cache (see {@link #refreshDeviceCache(android.bluetooth.BluetoothGatt, boolean)}).

- * - *

In contrast to {@link #EXTRA_RESTORE_BOND} this flag will not remove the old bonding and recreate a new one, but will keep the bond information untouched.

- *

The default value of this flag is false.

- * - *

This flag is ignored when Secure DFU Buttonless Service is used. It will keep or remove the bond depending on the Buttonless service type.

+ * This flag indicated whether the bond information should be kept or removed after an upgrade + * of the Application. If an application is being updated on a bonded device with the DFU + * Bootloader that has been configured to preserve the bond information for the new application, + * set it to true. + *

+ * By default the Legacy DFU Bootloader clears the whole application's memory. It may be, + * however, configured in the \Nordic\nrf51\components\libraries\bootloader_dfu\dfu_types.h + * file (sdk 11, line 76: #define DFU_APP_DATA_RESERVED 0x0000) to preserve some pages. + * The BLE_APP_HRM_DFU sample app stores the LTK and System Attributes in the first + * two pages, so in order to preserve the bond information this value should be changed to + * 0x0800 or more. For Secure DFU this value is by default set to 3 pages. + * When those data are preserved, the new Application will notify the app with the + * Service Changed indication when launched for the first time. Otherwise this service will + * remove the bond information from the phone and force to refresh the device cache + * (see {@link #refreshDeviceCache(android.bluetooth.BluetoothGatt, boolean)}). + *

+ * In contrast to {@link #EXTRA_RESTORE_BOND} this flag will not remove the old bonding and + * recreate a new one, but will keep the bond information untouched. + *

+ * The default value of this flag is false. + *

+ * This flag is ignored when Secure DFU Buttonless Service is used. It will keep or remove the + * bond depending on the Buttonless service type. */ public static final String EXTRA_KEEP_BOND = "no.nordicsemi.android.dfu.extra.EXTRA_KEEP_BOND"; /** * This property must contain a boolean value. - *

The {@link DfuBaseService}, when connected to a DFU target will check whether it is in application or in DFU bootloader mode. For DFU implementations from SDK 7.0 or newer - * this is done by reading the value of DFU Version characteristic. If the returned value is equal to 0x0100 (major = 0, minor = 1) it means that we are in the application mode and - * jump to the bootloader mode is required. - *

However, for DFU implementations from older SDKs, where there was no DFU Version characteristic, the service must guess. If this option is set to false (default) it will count - * number of device's services. If the count is equal to 3 (Generic Access, Generic Attribute, DFU Service) it will assume that it's in DFU mode. If greater than 3 - in app mode. - * This guessing may not be always correct. One situation may be when the nRF chip is used to flash update on external MCU using DFU. The DFU procedure may be implemented in the - * application, which may (and usually does) have more services. In such case set the value of this property to true. + *

+ * The {@link DfuBaseService}, when connected to a DFU target will check whether it is in + * application or in DFU bootloader mode. For DFU implementations from SDK 7.0 or newer + * this is done by reading the value of DFU Version characteristic. + * If the returned value is equal to 0x0100 (major = 0, minor = 1) it means that we are in the + * application mode and jump to the bootloader mode is required. + *

+ * However, for DFU implementations from older SDKs, where there was no DFU Version + * characteristic, the service must guess. If this option is set to false (default) it will count + * number of device's services. If the count is equal to 3 (Generic Access, Generic Attribute, + * DFU Service) it will assume that it's in DFU mode. If greater than 3 - in app mode. + * This guessing may not be always correct. One situation may be when the nRF chip is used to + * flash update on external MCU using DFU. The DFU procedure may be implemented in the + * application, which may (and usually does) have more services. + * In such case set the value of this property to true. */ public static final String EXTRA_FORCE_DFU = "no.nordicsemi.android.dfu.extra.EXTRA_FORCE_DFU"; /** @@ -198,13 +231,14 @@ public abstract class DfuBaseService extends IntentService implements DfuProgres *

* In the SDK 12.x the Buttonless DFU feature for Secure DFU was experimental. * It is NOT recommended to use it: it was not properly tested, had implementation bugs - * (e.g. https://devzone.nordicsemi.com/question/100609/sdk-12-bootloader-erased-after-programming/) and - * does not required encryption and therefore may lead to DOS attack (anyone can use it to switch the device - * to bootloader mode). However, as there is no other way to trigger bootloader mode on devices - * without a button, this DFU Library supports this service, but the feature must be explicitly enabled here. - * Be aware, that setting this flag to false will no protect your devices from this kind of attacks, as - * an attacker may use another app for that purpose. To be sure your device is secure remove this - * experimental service from your device. + * (e.g. https://devzone.nordicsemi.com/question/100609/sdk-12-bootloader-erased-after-programming/) + * and does not required encryption and therefore may lead to DOS attack (anyone can use it + * to switch the device to bootloader mode). However, as there is no other way to trigger + * bootloader mode on devices without a button, this DFU Library supports this service, + * but the feature must be explicitly enabled here. + * Be aware, that setting this flag to false will no protect your devices from this kind of + * attacks, as an attacker may use another app for that purpose. To be sure your device is + * secure remove this experimental service from your device. *

* Spec:
* Buttonless DFU Service UUID: 8E400001-F315-4F60-9FB8-838830DAEA50
@@ -217,36 +251,53 @@ public abstract class DfuBaseService extends IntentService implements DfuProgres * The device should disconnect and restart in DFU mode after sending the notification. *

* In SDK 13 this issue will be fixed by a proper implementation (bonding required, - * passing bond information to the bootloader, encryption, well tested). It is recommended to use this - * new service when SDK 13 (or later) is out. TODO: fix the docs when SDK 13 is out. + * passing bond information to the bootloader, encryption, well tested). + * It is recommended to use this new service when SDK 13 (or later) is out. */ public static final String EXTRA_UNSAFE_EXPERIMENTAL_BUTTONLESS_DFU = "no.nordicsemi.android.dfu.extra.EXTRA_UNSAFE_EXPERIMENTAL_BUTTONLESS_DFU"; /** * This property must contain a boolean value. - *

If true the Packet Receipt Notification procedure will be enabled. See DFU documentation on http://infocenter.nordicsemi.com for more details. - * The number of packets before receiving a Packet Receipt Notification is set with property {@link #EXTRA_PACKET_RECEIPT_NOTIFICATIONS_VALUE}. - * The PRNs by default are enabled on devices running Android 4.3, 4.4.x and 5.x and disabled on 6.x and newer. + *

+ * If true the Packet Receipt Notification procedure will be enabled. + * See DFU documentation on http://infocenter.nordicsemi.com for more details. + * The number of packets before receiving a Packet Receipt Notification is set with property + * {@link #EXTRA_PACKET_RECEIPT_NOTIFICATIONS_VALUE}. + * The PRNs by default are enabled on devices running Android 4.3, 4.4.x and 5.x and + * disabled on 6.x and newer. + * * @see #EXTRA_PACKET_RECEIPT_NOTIFICATIONS_VALUE */ public static final String EXTRA_PACKET_RECEIPT_NOTIFICATIONS_ENABLED = "no.nordicsemi.android.dfu.extra.EXTRA_PRN_ENABLED"; /** * This property must contain a positive integer value, usually from range 1-200. - *

The default value is {@link DfuServiceInitiator#DEFAULT_PRN_VALUE}. Setting it to 0 will disable the Packet Receipt Notification procedure. - * When sending a firmware using the DFU procedure the service will send this number of packets before waiting for a notification. - * Packet Receipt Notifications are used to synchronize the sender with receiver. - *

On Android, calling {@link android.bluetooth.BluetoothGatt#writeCharacteristic(BluetoothGattCharacteristic)} - * simply adds the packet to outgoing queue before returning the callback. Adding the next packet in the callback is much faster than the real transmission - * (also the speed depends on the device chip manufacturer) and the queue may reach its limit. When does, the transmission stops and Android Bluetooth hangs (see Note below). - * Using PRN procedure eliminates this problem as the notification is send when all packets were delivered the queue is empty. - *

Note: this bug has been fixed on Android 6.0 Marshmallow and now no notifications are required. The onCharacteristicWrite callback will be - * postponed until half of the queue is empty and upload will be resumed automatically. Disabling PRNs speeds up the upload process on those devices. + *

+ * The default value is {@link DfuServiceInitiator#DEFAULT_PRN_VALUE}. + * Setting it to 0 will disable the Packet Receipt Notification procedure. + * When sending a firmware using the DFU procedure the service will send this number of packets + * before waiting for a notification. Packet Receipt Notifications are used to synchronize + * the sender with receiver. + *

+ * On Android, calling + * {@link android.bluetooth.BluetoothGatt#writeCharacteristic(BluetoothGattCharacteristic)} + * simply adds the packet to outgoing queue before returning the callback. Adding the next + * packet in the callback is much faster than the real transmission (also the speed depends on + * the device chip manufacturer) and the queue may reach its limit. When does, the transmission + * stops and Android Bluetooth hangs (see Note below). Using PRN procedure eliminates this + * problem as the notification is send when all packets were delivered the queue is empty. + *

+ * Note: this bug has been fixed on Android 6.0 Marshmallow and now no notifications are required. + * The onCharacteristicWrite callback will be postponed until half of the queue is empty and + * upload will be resumed automatically. Disabling PRNs speeds up the upload process on those + * devices. + * * @see #EXTRA_PACKET_RECEIPT_NOTIFICATIONS_ENABLED */ public static final String EXTRA_PACKET_RECEIPT_NOTIFICATIONS_VALUE = "no.nordicsemi.android.dfu.extra.EXTRA_PRN_VALUE"; /** * A path to the file with the new firmware. It may point to a HEX, BIN or a ZIP file. - * Some file manager applications return the path as a String while other return a Uri. Use the {@link #EXTRA_FILE_URI} in the later case. - * For files included in /res/raw resource directory please use {@link #EXTRA_FILE_RES_ID} instead. + * Some file manager applications return the path as a String while other return a Uri. + * Use the {@link #EXTRA_FILE_URI} in the later case. For files included + * in /res/raw resource directory please use {@link #EXTRA_FILE_RES_ID} instead. */ public static final String EXTRA_FILE_PATH = "no.nordicsemi.android.dfu.extra.EXTRA_FILE_PATH"; /** @@ -258,26 +309,33 @@ public abstract class DfuBaseService extends IntentService implements DfuProgres */ public static final String EXTRA_FILE_RES_ID = "no.nordicsemi.android.dfu.extra.EXTRA_FILE_RES_ID"; /** - * The Init packet URI. This file is required if the Extended Init Packet is required (SDK 7.0+). Must point to a 'dat' file corresponding with the selected firmware. - * The Init packet may contain just the CRC (in case of older versions of DFU) or the Extended Init Packet in binary format (SDK 7.0+). + * The Init packet URI. This file is required if the Extended Init Packet is required (SDK 7.0+). + * Must point to a 'dat' file corresponding with the selected firmware. + * The Init packet may contain just the CRC (in case of older versions of DFU) or the + * Extended Init Packet in binary format (SDK 7.0+). */ public static final String EXTRA_INIT_FILE_PATH = "no.nordicsemi.android.dfu.extra.EXTRA_INIT_FILE_PATH"; /** - * The Init packet URI. This file is required if the Extended Init Packet is required (SDK 7.0+). Must point to a 'dat' file corresponding with the selected firmware. - * The Init packet may contain just the CRC (in case of older versions of DFU) or the Extended Init Packet in binary format (SDK 7.0+). + * The Init packet URI. This file is required if the Extended Init Packet is required (SDK 7.0+). + * Must point to a 'dat' file corresponding with the selected firmware. + * The Init packet may contain just the CRC (in case of older versions of DFU) or the + * Extended Init Packet in binary format (SDK 7.0+). */ public static final String EXTRA_INIT_FILE_URI = "no.nordicsemi.android.dfu.extra.EXTRA_INIT_FILE_URI"; /** - * The Init packet URI. This file is required if the Extended Init Packet is required (SDK 7.0+). Must point to a 'dat' file corresponding with the selected firmware. - * The Init packet may contain just the CRC (in case of older versions of DFU) or the Extended Init Packet in binary format (SDK 7.0+). + * The Init packet URI. This file is required if the Extended Init Packet is required (SDK 7.0+). + * Must point to a 'dat' file corresponding with the selected firmware. + * The Init packet may contain just the CRC (in case of older versions of DFU) or the + * Extended Init Packet in binary format (SDK 7.0+). */ public static final String EXTRA_INIT_FILE_RES_ID = "no.nordicsemi.android.dfu.extra.EXTRA_INIT_FILE_RES_ID"; /** - * The input file mime-type. Currently only "application/zip" (ZIP) or "application/octet-stream" (HEX or BIN) are supported. If this parameter is - * empty the "application/octet-stream" is assumed. + * The input file mime-type. Currently only "application/zip" (ZIP) or "application/octet-stream" + * (HEX or BIN) are supported. If this parameter is empty the "application/octet-stream" is assumed. */ public static final String EXTRA_FILE_MIME_TYPE = "no.nordicsemi.android.dfu.extra.EXTRA_MIME_TYPE"; - // Since the DFU Library version 0.5 both HEX and BIN files are supported. As both files have the same MIME TYPE the distinction is made based on the file extension. + // Since the DFU Library version 0.5 both HEX and BIN files are supported. + // As both files have the same MIME TYPE the distinction is made based on the file extension. public static final String MIME_TYPE_OCTET_STREAM = "application/octet-stream"; public static final String MIME_TYPE_ZIP = "application/zip"; /** @@ -286,27 +344,32 @@ public abstract class DfuBaseService extends IntentService implements DfuProgres *

  • {@link #TYPE_SOFT_DEVICE} - only Soft Device update
  • *
  • {@link #TYPE_BOOTLOADER} - only Bootloader update
  • *
  • {@link #TYPE_APPLICATION} - only application update
  • - *
  • {@link #TYPE_AUTO} - the file is a ZIP file that may contain more than one HEX/BIN + DAT files. Since SDK 8.0 the ZIP Distribution packet is a recommended - * way of delivering firmware files. Please, see the DFU documentation for more details. A ZIP distribution packet may be created using the 'nrf utility' - * command line application, that is a part of Master Control Panel 3.8.0.The ZIP file MAY contain only the following files: - * softdevice.hex/bin, bootloader.hex/bin, application.hex/bin to determine the type based on its name. At lease one of them MUST be present. + *
  • {@link #TYPE_AUTO} - the file is a ZIP file that may contain more than one HEX/BIN + DAT files. + * Since SDK 8.0 the ZIP Distribution packet is a recommended way of delivering firmware files. + * Please, see the DFU documentation for more details. A ZIP distribution packet may be created + * using the 'nrf util' Python application, available at + * https://github.com/NordicSemiconductor/pc-nrfutil. + * The ZIP file MAY contain only the following files: softdevice.hex/bin, + * bootloader.hex/bin, application.hex/bin to determine the type based on its name. + * At lease one of them MUST be present. *
  • * * If this parameter is not provided the type is assumed as follows: *
      - *
    1. If the {@link #EXTRA_FILE_MIME_TYPE} field is null or is equal to {@value #MIME_TYPE_OCTET_STREAM} - the {@link #TYPE_APPLICATION} is assumed.
    2. - *
    3. If the {@link #EXTRA_FILE_MIME_TYPE} field is equal to {@value #MIME_TYPE_ZIP} - the {@link #TYPE_AUTO} is assumed.
    4. + *
    5. If the {@link #EXTRA_FILE_MIME_TYPE} field is null or is equal to + * {@value #MIME_TYPE_OCTET_STREAM} - the {@link #TYPE_APPLICATION} is assumed.
    6. + *
    7. If the {@link #EXTRA_FILE_MIME_TYPE} field is equal to {@value #MIME_TYPE_ZIP} + * - the {@link #TYPE_AUTO} is assumed.
    8. *
    */ public static final String EXTRA_FILE_TYPE = "no.nordicsemi.android.dfu.extra.EXTRA_FILE_TYPE"; /** *

    * The file contains a new version of Soft Device. - *

    *

    - * Since DFU Library 7.0 all firmware may contain an Init packet. The Init packet is required if Extended Init Packet is used by the DFU bootloader (SDK 7.0+).. + * Since DFU Library 7.0 all firmware may contain an Init packet. The Init packet is required + * if Extended Init Packet is used by the DFU bootloader (SDK 7.0+).. * The Init packet for the bootloader must be placed in the .dat file. - *

    * * @see #EXTRA_FILE_TYPE */ @@ -314,11 +377,10 @@ public abstract class DfuBaseService extends IntentService implements DfuProgres /** *

    * The file contains a new version of Bootloader. - *

    *

    - * Since DFU Library 7.0 all firmware may contain an Init packet. The Init packet is required if Extended Init Packet is used by the DFU bootloader (SDK 7.0+). + * Since DFU Library 7.0 all firmware may contain an Init packet. The Init packet is required + * if Extended Init Packet is used by the DFU bootloader (SDK 7.0+). * The Init packet for the bootloader must be placed in the .dat file. - *

    * * @see #EXTRA_FILE_TYPE */ @@ -326,31 +388,37 @@ public abstract class DfuBaseService extends IntentService implements DfuProgres /** *

    * The file contains a new version of Application. - *

    *

    - * Since DFU Library 0.5 all firmware may contain an Init packet. The Init packet is required if Extended Init Packet is used by the DFU bootloader (SDK 7.0+). + * Since DFU Library 0.5 all firmware may contain an Init packet. The Init packet is required + * if Extended Init Packet is used by the DFU bootloader (SDK 7.0+). * The Init packet for the application must be placed in the .dat file. - *

    * * @see #EXTRA_FILE_TYPE */ public static final int TYPE_APPLICATION = 0x04; /** *

    - * A ZIP file that consists of more than 1 file. Since SDK 8.0 the ZIP Distribution packet is a recommended way of delivering firmware files. Please, see the DFU documentation for - * more details. A ZIP distribution packet may be created using the 'nrf utility' command line application, that is a part of Master Control Panel 3.8.0. - * For backwards compatibility this library supports also ZIP files without the manifest file. Instead they must follow the fixed naming convention: - * The names of files in the ZIP must be: softdevice.hex (or .bin), bootloader.hex (or .bin), application.hex (or .bin) in order - * to be read correctly. Using the Soft Device v7.0.0+ the Soft Device and Bootloader may be updated and sent together. In case of additional application file included, - * the service will try to send Soft Device, Bootloader and Application together (which is not supported currently) and if it fails, send first SD+BL, reconnect and send the application - * in the following connection. - *

    + * A ZIP file that consists of more than 1 file. Since SDK 8.0 the ZIP Distribution packet is + * a recommended way of delivering firmware files. Please, see the DFU documentation for + * more details. A ZIP distribution packet may be created using the 'nrf utility' command line + * application, that is a part of Master Control Panel 3.8.0. + * For backwards compatibility this library supports also ZIP files without the manifest file. + * Instead they must follow the fixed naming convention: + * The names of files in the ZIP must be: softdevice.hex (or .bin), bootloader.hex + * (or .bin), application.hex (or .bin) in order to be read correctly. Using the + * Soft Device v7.0.0+ the Soft Device and Bootloader may be updated and sent together. + * In case of additional application file included, the service will try to send Soft Device, + * Bootloader and Application together (which is not supported currently) and if it fails, + * send first SD+BL, reconnect and send the application in the following connection. *

    - * Since the DFU Library 0.5 you may specify the Init packet, that will be send prior to the firmware. The init packet contains some verification data, like a device type and - * revision, application version or a list of supported Soft Devices. The Init packet is required if Extended Init Packet is used by the DFU bootloader (SDK 7.0+). - * In case of using the compatibility ZIP files the Init packet for the Soft Device and Bootloader must be in the 'system.dat' file while for the application - * in the 'application.dat' file (included in the ZIP). The CRC in the 'system.dat' must be a CRC of both BIN contents if both a Soft Device and a Bootloader is present. - *

    + * Since the DFU Library 0.5 you may specify the Init packet, that will be send prior to the + * firmware. The init packet contains some verification data, like a device type and revision, + * application version or a list of supported Soft Devices. The Init packet is required if + * Extended Init Packet is used by the DFU bootloader (SDK 7.0+). + * In case of using the compatibility ZIP files the Init packet for the Soft Device and Bootloader + * must be in the 'system.dat' file while for the application in the 'application.dat' file + * (included in the ZIP). The CRC in the 'system.dat' must be a CRC of both BIN contents if + * both a Soft Device and a Bootloader is present. * * @see #EXTRA_FILE_TYPE */ @@ -360,7 +428,8 @@ public abstract class DfuBaseService extends IntentService implements DfuProgres */ public static final String EXTRA_DATA = "no.nordicsemi.android.dfu.extra.EXTRA_DATA"; /** - * An extra field to send the progress or error information in the DFU notification. The value may contain: + * An extra field to send the progress or error information in the DFU notification. + * The value may contain: * * To check if error occurred use:
    * {@code boolean error = progressValue >= DfuBaseService.ERROR_MASK;} */ public static final String EXTRA_PROGRESS = "no.nordicsemi.android.dfu.extra.EXTRA_PROGRESS"; /** - * The number of currently transferred part. The SoftDevice and Bootloader may be send together as one part. If user wants to upload them together with an application it has to be sent - * in another connection as the second part. + * The number of currently transferred part. The SoftDevice and Bootloader may be send + * together as one part. If user wants to upload them together with an application it has to be + * sent in another connection as the second part. * * @see no.nordicsemi.android.dfu.DfuBaseService#EXTRA_PARTS_TOTAL */ @@ -419,11 +490,13 @@ public abstract class DfuBaseService extends IntentService implements DfuProgres * *
  • {@link #EXTRA_DEVICE_ADDRESS} - the target device address
  • *
  • {@link #EXTRA_PART_CURRENT} - the number of currently transmitted part
  • - *
  • {@link #EXTRA_PARTS_TOTAL} - total number of parts that are being sent, f.e. if a ZIP file contains a Soft Device, a Bootloader and an Application, - * the SoftDevice and Bootloader will be send together as one part. Then the service will disconnect and reconnect to the new Bootloader and send the - * application as part number two.
  • + *
  • {@link #EXTRA_PARTS_TOTAL} - total number of parts that are being sent, e.g. if a ZIP + * file contains a Soft Device, a Bootloader and an Application, the SoftDevice and Bootloader + * will be send together as one part. Then the service will disconnect and reconnect to the + * new Bootloader and send the application as part number two.
  • *
  • {@link #EXTRA_SPEED_B_PER_MS} - current speed in bytes/millisecond as float
  • - *
  • {@link #EXTRA_AVG_SPEED_B_PER_MS} - the average transmission speed in bytes/millisecond as float
  • + *
  • {@link #EXTRA_AVG_SPEED_B_PER_MS} - the average transmission speed in bytes/millisecond + * as float
  • * */ public static final String BROADCAST_PROGRESS = "no.nordicsemi.android.dfu.broadcast.BROADCAST_PROGRESS"; @@ -436,8 +509,9 @@ public abstract class DfuBaseService extends IntentService implements DfuProgres */ public static final int PROGRESS_STARTING = -2; /** - * Service has triggered a switch to bootloader mode. Now the service waits for the link loss event (this may take up to several seconds) and will connect again - * to the same device, now started in the bootloader mode. + * Service has triggered a switch to bootloader mode. Now the service waits for the link loss + * event (this may take up to several seconds) and will connect again to the same device, + * now started in the bootloader mode. */ public static final int PROGRESS_ENABLING_DFU_MODE = -3; /** @@ -459,15 +533,19 @@ public abstract class DfuBaseService extends IntentService implements DfuProgres /** * The broadcast error message contains the following extras: * */ public static final String BROADCAST_ERROR = "no.nordicsemi.android.dfu.broadcast.BROADCAST_ERROR"; /** - * The type of the error. This extra contains information about that kind of error has occurred. Connection state errors and other errors may share the same numbers. - * For example, the {@link BluetoothGattCallback#onCharacteristicWrite(BluetoothGatt, BluetoothGattCharacteristic, int)} method may return a status code 8 (GATT INSUF AUTHORIZATION), - * while the status code 8 returned by {@link BluetoothGattCallback#onConnectionStateChange(BluetoothGatt, int, int)} is a GATT CONN TIMEOUT error. + * The type of the error. This extra contains information about that kind of error has occurred. + * Connection state errors and other errors may share the same numbers. For example, the + * {@link BluetoothGattCallback#onCharacteristicWrite(BluetoothGatt, BluetoothGattCharacteristic, int)} + * method may return a status code 8 (GATT INSUF AUTHORIZATION), while the status code 8 + * returned by {@link BluetoothGattCallback#onConnectionStateChange(BluetoothGatt, int, int)} + * is a GATT CONN TIMEOUT error. */ public static final String EXTRA_ERROR_TYPE = "no.nordicsemi.android.dfu.extra.EXTRA_ERROR_TYPE"; public static final int ERROR_TYPE_OTHER = 0; @@ -475,7 +553,8 @@ public abstract class DfuBaseService extends IntentService implements DfuProgres public static final int ERROR_TYPE_COMMUNICATION = 2; public static final int ERROR_TYPE_DFU_REMOTE = 3; /** - * If this bit is set than the progress value indicates an error. Use {@link GattError#parse(int)} to obtain error name. + * If this bit is set than the progress value indicates an error. Use {@link GattError#parse(int)} + * to obtain error name. */ public static final int ERROR_MASK = 0x1000; public static final int ERROR_DEVICE_DISCONNECTED = ERROR_MASK; // | 0x00; @@ -497,11 +576,13 @@ public abstract class DfuBaseService extends IntentService implements DfuProgres */ public static final int ERROR_SERVICE_DISCOVERY_NOT_STARTED = ERROR_MASK | 0x05; /** - * Thrown when the service discovery has finished but the DFU service has not been found. The device does not support DFU of is not in DFU mode. + * Thrown when the service discovery has finished but the DFU service has not been found. + * The device does not support DFU of is not in DFU mode. */ public static final int ERROR_SERVICE_NOT_FOUND = ERROR_MASK | 0x06; /** - * Thrown when unknown response has been obtained from the target. The DFU target must follow specification. + * Thrown when unknown response has been obtained from the target. The DFU target must follow + * specification. */ public static final int ERROR_INVALID_RESPONSE = ERROR_MASK | 0x08; /** @@ -513,15 +594,18 @@ public abstract class DfuBaseService extends IntentService implements DfuProgres */ public static final int ERROR_BLUETOOTH_DISABLED = ERROR_MASK | 0x0A; /** - * DFU Bootloader version 0.6+ requires sending the Init packet. If such bootloader version is detected, but the init packet has not been set this error is thrown. + * DFU Bootloader version 0.6+ requires sending the Init packet. If such bootloader version is + * detected, but the init packet has not been set this error is thrown. */ public static final int ERROR_INIT_PACKET_REQUIRED = ERROR_MASK | 0x0B; - /** - * Thrown when the firmware file is not word-aligned. The firmware size must be dividable by 4 bytes. - */ - public static final int ERROR_FILE_SIZE_INVALID = ERROR_MASK | 0x0C; /** - * Thrown when the received CRC does not match with the calculated one. The service will try 3 times to send the data, and if the CRC fails each time this error will be thrown. + * Thrown when the firmware file is not word-aligned. The firmware size must be dividable by + * 4 bytes. + */ + public static final int ERROR_FILE_SIZE_INVALID = ERROR_MASK | 0x0C; + /** + * Thrown when the received CRC does not match with the calculated one. The service will try + * 3 times to send the data, and if the CRC fails each time this error will be thrown. */ public static final int ERROR_CRC_ERROR = ERROR_MASK | 0x0D; /** @@ -529,8 +613,9 @@ public abstract class DfuBaseService extends IntentService implements DfuProgres */ public static final int ERROR_DEVICE_NOT_BONDED = ERROR_MASK | 0x0E; /** - * Flag set when the DFU target returned a DFU error. Look for DFU specification to get error codes. The error code is binary OR-ed with one of: - * {@link #ERROR_REMOTE_TYPE_LEGACY}, {@link #ERROR_REMOTE_TYPE_SECURE} or {@link #ERROR_REMOTE_TYPE_SECURE_EXTENDED}. + * Flag set when the DFU target returned a DFU error. Look for DFU specification to get error + * codes. The error code is binary OR-ed with one of: {@link #ERROR_REMOTE_TYPE_LEGACY}, + * {@link #ERROR_REMOTE_TYPE_SECURE} or {@link #ERROR_REMOTE_TYPE_SECURE_EXTENDED}. */ public static final int ERROR_REMOTE_MASK = 0x2000; public static final int ERROR_REMOTE_TYPE_LEGACY = 0x0100; @@ -538,19 +623,23 @@ public abstract class DfuBaseService extends IntentService implements DfuProgres public static final int ERROR_REMOTE_TYPE_SECURE_EXTENDED = 0x0400; public static final int ERROR_REMOTE_TYPE_SECURE_BUTTONLESS = 0x0800; /** - * The flag set when one of {@link android.bluetooth.BluetoothGattCallback} methods was called with status other than {@link android.bluetooth.BluetoothGatt#GATT_SUCCESS}. + * The flag set when one of {@link android.bluetooth.BluetoothGattCallback} methods was called + * with status other than {@link android.bluetooth.BluetoothGatt#GATT_SUCCESS}. */ public static final int ERROR_CONNECTION_MASK = 0x4000; /** - * The flag set when the {@link android.bluetooth.BluetoothGattCallback#onConnectionStateChange(android.bluetooth.BluetoothGatt, int, int)} method was called with - * status other than {@link android.bluetooth.BluetoothGatt#GATT_SUCCESS}. + * The flag set when the + * {@link android.bluetooth.BluetoothGattCallback#onConnectionStateChange(android.bluetooth.BluetoothGatt, int, int)} + * method was called with status other than {@link android.bluetooth.BluetoothGatt#GATT_SUCCESS}. */ public static final int ERROR_CONNECTION_STATE_MASK = 0x8000; /** - * The log events are only broadcast when there is no nRF Logger installed. The broadcast contains 2 extras: + * The log events are only broadcast when there is no nRF Logger installed. + * The broadcast contains 2 extras: * */ @@ -560,8 +649,8 @@ public abstract class DfuBaseService extends IntentService implements DfuProgres /* * Note: * The nRF Logger API library has been excluded from the DfuLibrary. - * All log events are now being sent using local broadcasts and may be logged into nRF Logger in the app module. - * This is to make the Dfu module independent from logging tool. + * All log events are now being sent using local broadcasts and may be logged into nRF Logger + * in the app module. This is to make the Dfu module independent from logging tool. * * The log levels below are equal to log levels in nRF Logger API library, v 2.0. * @see https://github.com/NordicSemiconductor/nRF-Logger-API @@ -596,18 +685,24 @@ public abstract class DfuBaseService extends IntentService implements DfuProgres */ public static final String BROADCAST_ACTION = "no.nordicsemi.android.dfu.broadcast.BROADCAST_ACTION"; /** - * The action extra. It may have one of the following values: {@link #ACTION_PAUSE}, {@link #ACTION_RESUME}, {@link #ACTION_ABORT}. + * The action extra. It may have one of the following values: {@link #ACTION_PAUSE}, + * {@link #ACTION_RESUME}, {@link #ACTION_ABORT}. */ public static final String EXTRA_ACTION = "no.nordicsemi.android.dfu.extra.EXTRA_ACTION"; - /** Pauses the upload. The service will wait for broadcasts with the action set to {@link #ACTION_RESUME} or {@link #ACTION_ABORT}. */ + /** + * Pauses the upload. The service will wait for broadcasts with the action set to + * {@link #ACTION_RESUME} or {@link #ACTION_ABORT}. + */ public static final int ACTION_PAUSE = 0; /** Resumes the upload that has been paused before using {@link #ACTION_PAUSE}. */ public static final int ACTION_RESUME = 1; /** * Aborts the upload. The service does not need to be paused before. - * After sending {@link #BROADCAST_ACTION} with extra {@link #EXTRA_ACTION} set to this value the DFU bootloader will restore the old application - * (if there was already an application). Be aware that uploading the Soft Device will erase the application in order to make space in the memory. - * In case there is no application, or the application has been removed, the DFU bootloader will be started and user may try to send the application again. + * After sending {@link #BROADCAST_ACTION} with extra {@link #EXTRA_ACTION} set to this value + * the DFU bootloader will restore the old application (if there was already an application). + * Be aware, that uploading the Soft Device will erase the application in order to make space + * in the memory. In case there is no application, or the application has been removed, the + * DFU bootloader will be started and user may try to send the application again. * The bootloader may advertise with the address incremented by 1 to prevent caching services. */ public static final int ACTION_ABORT = 2; @@ -627,7 +722,8 @@ public abstract class DfuBaseService extends IntentService implements DfuProgres private String mDeviceName; private boolean mDisableNotification; /** - * The current connection state. If its value is > 0 than an error has occurred. Error number is a negative value of mConnectionState + * The current connection state. If its value is > 0 than an error has occurred. + * Error number is a negative value of mConnectionState */ protected int mConnectionState; protected final static int STATE_DISCONNECTED = 0; @@ -641,7 +737,8 @@ public abstract class DfuBaseService extends IntentService implements DfuProgres */ private int mError; /** - * Stores the last progress percent. Used to prevent from sending progress notifications with the same value. + * Stores the last progress percent. Used to prevent from sending progress notifications with + * the same value. */ private int mLastProgress = -1; /* package */ DfuProgressInfo mProgressInfo; @@ -1003,8 +1100,10 @@ public abstract class DfuBaseService extends IntentService implements DfuProgres mConnectionState = STATE_DISCONNECTED; mError = 0; - // The Soft Device starts where MBR ends (by default from the address 0x1000). Before there is a MBR section, which should not be transmitted over DFU. - // Applications and bootloader starts from bigger address. However, in custom DFU implementations, user may want to transmit the whole whole data, even from address 0x0000. + // The Soft Device starts where MBR ends (by default from the address 0x1000). + // Before there is a MBR section, which should not be transmitted over DFU. + // Applications and bootloader starts from bigger address. However, in custom DFU + // implementations, user may want to transmit the whole whole data, even from address 0x0000. final SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this); final String value = preferences.getString(DfuSettingsConstants.SETTINGS_MBR_SIZE, String.valueOf(DfuSettingsConstants.SETTINGS_DEFAULT_MBR_SIZE)); int mbrSize; @@ -1796,7 +1895,7 @@ public abstract class DfuBaseService extends IntentService implements DfuProgres } private void loge(final String message) { - Log.e(TAG, message); + Log.e(TAG, message); } private void loge(final String message, final Throwable e) { From f303c4ab4b3b24f85c8d79e296f95e8f67417404 Mon Sep 17 00:00:00 2001 From: Aleksander Nowakowski Date: Wed, 20 Feb 2019 09:59:59 +0100 Subject: [PATCH 04/13] Warnings removed --- .../java/no/nordicsemi/android/dfu/DfuBaseService.java | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/dfu/src/main/java/no/nordicsemi/android/dfu/DfuBaseService.java b/dfu/src/main/java/no/nordicsemi/android/dfu/DfuBaseService.java index 67b3d5b..64b2c40 100644 --- a/dfu/src/main/java/no/nordicsemi/android/dfu/DfuBaseService.java +++ b/dfu/src/main/java/no/nordicsemi/android/dfu/DfuBaseService.java @@ -1579,11 +1579,10 @@ public abstract class DfuBaseService extends IntentService implements DfuProgres * There is a refresh() method in BluetoothGatt class but for now it's hidden. We will call it using reflections. */ try { + //noinspection JavaReflectionMemberAccess final Method refresh = gatt.getClass().getMethod("refresh"); - if (refresh != null) { - final boolean success = (Boolean) refresh.invoke(gatt); - logi("Refreshing result: " + success); - } + final boolean success = (Boolean) refresh.invoke(gatt); + logi("Refreshing result: " + success); } catch (Exception e) { loge("An exception occurred while refreshing device", e); sendLogBroadcast(LOG_LEVEL_WARNING, "Refreshing failed"); @@ -1738,6 +1737,7 @@ public abstract class DfuBaseService extends IntentService implements DfuProgres * This method allows you to update the notification showing an error. * @param builder error notification builder */ + @SuppressWarnings("unused") protected void updateErrorNotification(final NotificationCompat.Builder builder) { // Empty default implementation } @@ -1774,6 +1774,7 @@ public abstract class DfuBaseService extends IntentService implements DfuProgres * This method allows you to update the notification that will be shown when the service goes to the foreground state. * @param builder foreground notification builder */ + @SuppressWarnings("unused") protected void updateForegroundNotification(final NotificationCompat.Builder builder) { // Empty default implementation } @@ -1876,6 +1877,7 @@ public abstract class DfuBaseService extends IntentService implements DfuProgres * * @return true if initialization was successful */ + @SuppressWarnings("UnusedReturnValue") private boolean initialize() { // For API level 18 and above, get a reference to BluetoothAdapter through // BluetoothManager. From 85e7a002e479aafcd44ec6c1699833197257b149 Mon Sep 17 00:00:00 2001 From: Aleksander Nowakowski Date: Wed, 20 Feb 2019 10:04:30 +0100 Subject: [PATCH 05/13] More annotations added --- .../no/nordicsemi/android/dfu/DfuScope.java | 10 +++++ .../android/dfu/DfuServiceInitiator.java | 37 ++++++++++++------- .../no/nordicsemi/android/dfu/FileType.java | 13 +++++++ 3 files changed, 46 insertions(+), 14 deletions(-) create mode 100644 dfu/src/main/java/no/nordicsemi/android/dfu/DfuScope.java create mode 100644 dfu/src/main/java/no/nordicsemi/android/dfu/FileType.java diff --git a/dfu/src/main/java/no/nordicsemi/android/dfu/DfuScope.java b/dfu/src/main/java/no/nordicsemi/android/dfu/DfuScope.java new file mode 100644 index 0000000..31323c7 --- /dev/null +++ b/dfu/src/main/java/no/nordicsemi/android/dfu/DfuScope.java @@ -0,0 +1,10 @@ +package no.nordicsemi.android.dfu; + +import androidx.annotation.IntDef; + +@IntDef(value = { + DfuServiceInitiator.SCOPE_SYSTEM_COMPONENTS, + DfuServiceInitiator.SCOPE_APPLICATION + }, + flag = true) +public @interface DfuScope {} diff --git a/dfu/src/main/java/no/nordicsemi/android/dfu/DfuServiceInitiator.java b/dfu/src/main/java/no/nordicsemi/android/dfu/DfuServiceInitiator.java index b455529..e719756 100644 --- a/dfu/src/main/java/no/nordicsemi/android/dfu/DfuServiceInitiator.java +++ b/dfu/src/main/java/no/nordicsemi/android/dfu/DfuServiceInitiator.java @@ -33,8 +33,11 @@ import android.net.Uri; import android.os.Build; import android.os.ParcelUuid; import android.os.Parcelable; + +import androidx.annotation.IntRange; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import androidx.annotation.RawRes; import androidx.annotation.RequiresApi; import java.security.InvalidParameterException; @@ -50,9 +53,9 @@ public class DfuServiceInitiator { public static final int DEFAULT_PRN_VALUE = 12; /** Constant used to narrow the scope of the update to system components (SD+BL) only. */ - public static final int SCOPE_SYSTEM_COMPONENTS = 7578; + public static final int SCOPE_SYSTEM_COMPONENTS = 1; /** Constant used to narrow the scope of the update to application only. */ - public static final int SCOPE_APPLICATION = 3542; + public static final int SCOPE_APPLICATION = 2; private final String deviceAddress; private String deviceName; @@ -195,13 +198,17 @@ public class DfuServiceInitiator { /** * If Packet Receipt Notification procedure is enabled, this method sets number of packets to * be sent before receiving a PRN. A PRN is used to synchronize the transmitter and receiver. + *

    + * If the value given is equal to 0, the {@link #DEFAULT_PRN_VALUE} will be used instead. + *

    + * To disable PRNs use {@link #setPacketsReceiptNotificationsEnabled(boolean)}. * * @param number number of packets to be sent before receiving a PRN. Defaulted when set to 0. * @return the builder * @see #setPacketsReceiptNotificationsEnabled(boolean) * @see DfuSettingsConstants#SETTINGS_NUMBER_OF_PACKETS */ - public DfuServiceInitiator setPacketsReceiptNotificationsValue(final int number) { + public DfuServiceInitiator setPacketsReceiptNotificationsValue(@IntRange(from = 0) final int number) { this.numberOfPackets = number > 0 ? number : DEFAULT_PRN_VALUE; return this; } @@ -282,7 +289,7 @@ public class DfuServiceInitiator { * @param mtu the MTU that wil be requested, 0 to disable MTU request. * @return the builder */ - public DfuServiceInitiator setMtu(final int mtu) { + public DfuServiceInitiator setMtu(@IntRange(from = 23, to = 517) final int mtu) { this.mtu = mtu; return this; } @@ -304,7 +311,7 @@ public class DfuServiceInitiator { * {@link android.bluetooth.BluetoothGattServerCallback#onMtuChanged(BluetoothDevice, int)}. * @return the builder */ - public DfuServiceInitiator setCurrentMtu(final int mtu) { + public DfuServiceInitiator setCurrentMtu(@IntRange(from = 23, to = 517) final int mtu) { this.currentMtu = mtu; return this; } @@ -331,13 +338,15 @@ public class DfuServiceInitiator { * {@link #SCOPE_APPLICATION}. * @return the builder */ - public DfuServiceInitiator setScope(final int scope) { + public DfuServiceInitiator setScope(@DfuScope final int scope) { if (!DfuBaseService.MIME_TYPE_ZIP.equals(mimeType)) throw new UnsupportedOperationException("Scope can be set only for a ZIP file"); if (scope == SCOPE_APPLICATION) fileType = DfuBaseService.TYPE_APPLICATION; else if (scope == SCOPE_SYSTEM_COMPONENTS) fileType = DfuBaseService.TYPE_SOFT_DEVICE | DfuBaseService.TYPE_BOOTLOADER; + else if (scope == (SCOPE_APPLICATION | SCOPE_SYSTEM_COMPONENTS)) + fileType = DfuBaseService.TYPE_AUTO; else throw new UnsupportedOperationException("Unknown scope"); return this; } @@ -526,7 +535,7 @@ public class DfuServiceInitiator { * @see #setZip(Uri) * @see #setZip(String) */ - public DfuServiceInitiator setZip(final int rawResId) { + public DfuServiceInitiator setZip(@RawRes final int rawResId) { return init(null, null, rawResId, DfuBaseService.TYPE_AUTO, DfuBaseService.MIME_TYPE_ZIP); } @@ -558,7 +567,7 @@ public class DfuServiceInitiator { * @return the builder */ @Deprecated - public DfuServiceInitiator setBinOrHex(final int fileType, @NonNull final Uri uri) { + public DfuServiceInitiator setBinOrHex(@FileType final int fileType, @NonNull final Uri uri) { if (fileType == DfuBaseService.TYPE_AUTO) throw new UnsupportedOperationException("You must specify the file type"); return init(uri, null, 0, fileType, DfuBaseService.MIME_TYPE_OCTET_STREAM); @@ -574,7 +583,7 @@ public class DfuServiceInitiator { * @return the builder */ @Deprecated - public DfuServiceInitiator setBinOrHex(final int fileType, @NonNull final String path) { + public DfuServiceInitiator setBinOrHex(@FileType final int fileType, @NonNull final String path) { if (fileType == DfuBaseService.TYPE_AUTO) throw new UnsupportedOperationException("You must specify the file type"); return init(null, path, 0, fileType, DfuBaseService.MIME_TYPE_OCTET_STREAM); @@ -592,7 +601,7 @@ public class DfuServiceInitiator { * @deprecated The Distribution packet (ZIP) should be used for DFU Bootloader version 0.5 or newer */ @Deprecated - public DfuServiceInitiator setBinOrHex(final int fileType, @Nullable final Uri uri, @Nullable final String path) { + public DfuServiceInitiator setBinOrHex(@FileType final int fileType, @Nullable final Uri uri, @Nullable final String path) { if (fileType == DfuBaseService.TYPE_AUTO) throw new UnsupportedOperationException("You must specify the file type"); return init(uri, path, 0, fileType, DfuBaseService.MIME_TYPE_OCTET_STREAM); @@ -608,7 +617,7 @@ public class DfuServiceInitiator { * @return the builder */ @Deprecated - public DfuServiceInitiator setBinOrHex(final int fileType, final int rawResId) { + public DfuServiceInitiator setBinOrHex(@FileType final int fileType, @RawRes final int rawResId) { if (fileType == DfuBaseService.TYPE_AUTO) throw new UnsupportedOperationException("You must specify the file type"); return init(null, null, rawResId, fileType, DfuBaseService.MIME_TYPE_OCTET_STREAM); @@ -649,7 +658,7 @@ public class DfuServiceInitiator { * @return the builder */ @Deprecated - public DfuServiceInitiator setInitFile(final int initFileResId) { + public DfuServiceInitiator setInitFile(@RawRes final int initFileResId) { return init(null, null, initFileResId); } @@ -732,7 +741,7 @@ public class DfuServiceInitiator { private DfuServiceInitiator init(@Nullable final Uri initFileUri, @Nullable final String initFilePath, - final int initFileResId) { + @RawRes final int initFileResId) { if (DfuBaseService.MIME_TYPE_ZIP.equals(mimeType)) throw new InvalidParameterException("Init file must be located inside the ZIP"); @@ -744,7 +753,7 @@ public class DfuServiceInitiator { private DfuServiceInitiator init(@Nullable final Uri fileUri, @Nullable final String filePath, - final int fileResId, final int fileType, + @RawRes final int fileResId, @FileType final int fileType, @NonNull final String mimeType) { this.fileUri = fileUri; this.filePath = filePath; diff --git a/dfu/src/main/java/no/nordicsemi/android/dfu/FileType.java b/dfu/src/main/java/no/nordicsemi/android/dfu/FileType.java new file mode 100644 index 0000000..def984c --- /dev/null +++ b/dfu/src/main/java/no/nordicsemi/android/dfu/FileType.java @@ -0,0 +1,13 @@ +package no.nordicsemi.android.dfu; + +import androidx.annotation.IntDef; + +@IntDef(value = { + DfuBaseService.TYPE_SOFT_DEVICE, + DfuBaseService.TYPE_BOOTLOADER, + DfuBaseService.TYPE_APPLICATION + }, + flag = true) +public @interface FileType { +} + From 00805e1fc2c19c940a00c955eab0819e8fd8df7b Mon Sep 17 00:00:00 2001 From: Aleksander Nowakowski Date: Wed, 20 Feb 2019 10:06:22 +0100 Subject: [PATCH 06/13] Retrying DFU --- .../nordicsemi/android/dfu/BaseDfuImpl.java | 4 ++++ .../android/dfu/DfuBaseService.java | 12 +++++++++- .../android/dfu/DfuServiceInitiator.java | 24 +++++++++++++++++++ .../nordicsemi/android/dfu/LegacyDfuImpl.java | 1 - .../nordicsemi/android/dfu/SecureDfuImpl.java | 9 +++++-- 5 files changed, 46 insertions(+), 4 deletions(-) diff --git a/dfu/src/main/java/no/nordicsemi/android/dfu/BaseDfuImpl.java b/dfu/src/main/java/no/nordicsemi/android/dfu/BaseDfuImpl.java index 9d86e13..a5788af 100644 --- a/dfu/src/main/java/no/nordicsemi/android/dfu/BaseDfuImpl.java +++ b/dfu/src/main/java/no/nordicsemi/android/dfu/BaseDfuImpl.java @@ -707,6 +707,10 @@ import no.nordicsemi.android.dfu.internal.scanner.BootloaderScannerFactory; if (newAddress != null) intent.putExtra(DfuBaseService.EXTRA_DEVICE_ADDRESS, newAddress); + + // Reset the DFU attempt counter + intent.putExtra(DfuBaseService.EXTRA_DFU_ATTEMPT, 0); + mService.startService(intent); } diff --git a/dfu/src/main/java/no/nordicsemi/android/dfu/DfuBaseService.java b/dfu/src/main/java/no/nordicsemi/android/dfu/DfuBaseService.java index 64b2c40..f9941cc 100644 --- a/dfu/src/main/java/no/nordicsemi/android/dfu/DfuBaseService.java +++ b/dfu/src/main/java/no/nordicsemi/android/dfu/DfuBaseService.java @@ -1347,9 +1347,19 @@ public abstract class DfuBaseService extends IntentService implements DfuProgres mProgressInfo.setProgress(PROGRESS_ABORTED); } catch (final DeviceDisconnectedException e) { sendLogBroadcast(LOG_LEVEL_ERROR, "Device has disconnected"); - // TODO reconnect n times? loge(e.getMessage()); close(gatt); + + final int attempt = intent.getIntExtra(EXTRA_DFU_ATTEMPT, 0); + final int limit = intent.getIntExtra(EXTRA_MAX_DFU_ATTEMPTS, 0); + if (attempt < limit) { + logi("Restarting the service (" + (attempt + 1) + " /" + limit + ")"); + final Intent newIntent = new Intent(); + newIntent.fillIn(intent, Intent.FILL_IN_COMPONENT | Intent.FILL_IN_PACKAGE); + newIntent.putExtra(EXTRA_DFU_ATTEMPT, attempt + 1); + startService(newIntent); + return; + } report(ERROR_DEVICE_DISCONNECTED); } catch (final DfuException e) { int error = e.getErrorNumber(); diff --git a/dfu/src/main/java/no/nordicsemi/android/dfu/DfuServiceInitiator.java b/dfu/src/main/java/no/nordicsemi/android/dfu/DfuServiceInitiator.java index e719756..b5438a4 100644 --- a/dfu/src/main/java/no/nordicsemi/android/dfu/DfuServiceInitiator.java +++ b/dfu/src/main/java/no/nordicsemi/android/dfu/DfuServiceInitiator.java @@ -79,6 +79,7 @@ public class DfuServiceInitiator { private boolean forceDfu = false; private boolean enableUnsafeExperimentalButtonlessDfu = false; private boolean disableResume = false; + private int numberOfRetries = 0; // 0 to be backwards compatible private Boolean packetReceiptNotificationsEnabled; private int numberOfPackets = 12; @@ -269,6 +270,28 @@ public class DfuServiceInitiator { return this; } + /** + * Sets the number of retries that the DFU service will use to complete DFU. The default + * value is 0, for backwards compatibility reason. + *

    + * If the given value is greater than 0, the service will restart itself at most {@code max} + * times in case of an undesired disconnection during DFU operation. This attempt counter + * is independent from another counter, for reconnection attempts, which is equal to 3. + * The latter one will be used when connection will fail with an error (possible packet + * collision or any other reason). After successful connection, the reconnection counter is + * reset, while the retry counter is cleared after a DFU finishes with success. + *

    + * The service will not try to retry DFU in case of any other error, for instance an error + * sent from the target device. + * + * @param max Maximum number of retires to complete DFU. Usually around 2. + * @return the builder + */ + public DfuServiceInitiator setNumberOfRetries(@IntRange(from = 0) final int max) { + this.numberOfRetries = max; + return this; + } + /** * Sets the Maximum Transfer Unit (MTU) value that the Secure DFU service will try to request * before performing DFU. By default, value 517 will be used, which is the highest supported @@ -704,6 +727,7 @@ public class DfuServiceInitiator { intent.putExtra(DfuBaseService.EXTRA_RESTORE_BOND, restoreBond); intent.putExtra(DfuBaseService.EXTRA_FORCE_DFU, forceDfu); intent.putExtra(DfuBaseService.EXTRA_DISABLE_RESUME, disableResume); + intent.putExtra(DfuBaseService.EXTRA_MAX_DFU_ATTEMPTS, numberOfRetries); if (mtu > 0) intent.putExtra(DfuBaseService.EXTRA_MTU, mtu); intent.putExtra(DfuBaseService.EXTRA_CURRENT_MTU, currentMtu); diff --git a/dfu/src/main/java/no/nordicsemi/android/dfu/LegacyDfuImpl.java b/dfu/src/main/java/no/nordicsemi/android/dfu/LegacyDfuImpl.java index a675c69..424d404 100644 --- a/dfu/src/main/java/no/nordicsemi/android/dfu/LegacyDfuImpl.java +++ b/dfu/src/main/java/no/nordicsemi/android/dfu/LegacyDfuImpl.java @@ -474,7 +474,6 @@ import no.nordicsemi.android.error.LegacyDfuError; } catch (final DeviceDisconnectedException e) { loge("Disconnected while sending data"); throw e; - // TODO reconnect? } final long endTime = SystemClock.elapsedRealtime(); diff --git a/dfu/src/main/java/no/nordicsemi/android/dfu/SecureDfuImpl.java b/dfu/src/main/java/no/nordicsemi/android/dfu/SecureDfuImpl.java index 87b2752..fa20f4c 100644 --- a/dfu/src/main/java/no/nordicsemi/android/dfu/SecureDfuImpl.java +++ b/dfu/src/main/java/no/nordicsemi/android/dfu/SecureDfuImpl.java @@ -391,8 +391,13 @@ class SecureDfuImpl extends BaseCustomDfuImpl { mService.sendLogBroadcast(DfuBaseService.LOG_LEVEL_APPLICATION, "Command object created"); } // Write Init data to the Packet Characteristic - logi("Sending " + (mInitPacketSizeInBytes - info.offset) + " bytes of init packet..."); - writeInitData(mPacketCharacteristic, crc32); + try { + logi("Sending " + (mInitPacketSizeInBytes - info.offset) + " bytes of init packet..."); + writeInitData(mPacketCharacteristic, crc32); + } catch (final DeviceDisconnectedException e) { + loge("Disconnected while sending init packet"); + throw e; + } final int crc = (int) (crc32.getValue() & 0xFFFFFFFFL); mService.sendLogBroadcast(DfuBaseService.LOG_LEVEL_APPLICATION, String.format(Locale.US, "Command object sent (CRC = %08X)", crc)); From ba2814a5d41c121f650a13bab2e68470743e430f Mon Sep 17 00:00:00 2001 From: Aleksander Nowakowski Date: Wed, 20 Feb 2019 12:52:26 +0100 Subject: [PATCH 07/13] More annotations and comments fixed --- .../android/dfu/BaseButtonlessDfuImpl.java | 7 +- .../android/dfu/BaseCustomDfuImpl.java | 201 ++++++++----- .../nordicsemi/android/dfu/BaseDfuImpl.java | 279 ++++++++++-------- .../android/dfu/ButtonlessDfuImpl.java | 52 ++-- .../dfu/ButtonlessDfuWithBondSharingImpl.java | 35 ++- .../ButtonlessDfuWithoutBondSharingImpl.java | 41 +-- .../android/dfu/DfuBaseService.java | 132 +++++---- .../nordicsemi/android/dfu/DfuCallback.java | 10 +- .../nordicsemi/android/dfu/DfuController.java | 2 +- .../android/dfu/DfuLogListener.java | 5 +- .../android/dfu/DfuProgressInfo.java | 5 +- .../android/dfu/DfuProgressListener.java | 104 ++++--- .../dfu/DfuProgressListenerAdapter.java | 28 +- .../no/nordicsemi/android/dfu/DfuScope.java | 1 + .../no/nordicsemi/android/dfu/DfuService.java | 45 ++- .../android/dfu/DfuServiceController.java | 13 +- .../android/dfu/DfuServiceListenerHelper.java | 85 +++--- .../android/dfu/DfuServiceProvider.java | 7 +- .../android/dfu/DfuSettingsConstants.java | 1 + .../dfu/ExperimentalButtonlessDfuImpl.java | 15 +- .../no/nordicsemi/android/dfu/FileType.java | 1 + .../android/dfu/LegacyButtonlessDfuImpl.java | 35 ++- .../nordicsemi/android/dfu/LegacyDfuImpl.java | 133 +++++---- .../nordicsemi/android/dfu/SecureDfuImpl.java | 229 +++++++++----- .../no/nordicsemi/android/dfu/UuidHelper.java | 4 +- .../dfu/internal/ArchiveInputStream.java | 152 ++++++---- .../dfu/internal/manifest/Manifest.java | 1 + .../dfu/internal/manifest/ManifestFile.java | 1 + 28 files changed, 999 insertions(+), 625 deletions(-) diff --git a/dfu/src/main/java/no/nordicsemi/android/dfu/BaseButtonlessDfuImpl.java b/dfu/src/main/java/no/nordicsemi/android/dfu/BaseButtonlessDfuImpl.java index b8f6cab..055f07f 100644 --- a/dfu/src/main/java/no/nordicsemi/android/dfu/BaseButtonlessDfuImpl.java +++ b/dfu/src/main/java/no/nordicsemi/android/dfu/BaseButtonlessDfuImpl.java @@ -27,6 +27,8 @@ import android.bluetooth.BluetoothGatt; import android.bluetooth.BluetoothGattCharacteristic; import android.content.Intent; +import androidx.annotation.NonNull; + /** * A base implementation of a buttonless service. The purpose of a buttonless service is to * switch a device into the DFU bootloader mode. @@ -51,7 +53,7 @@ import android.content.Intent; } } - BaseButtonlessDfuImpl(final Intent intent, final DfuBaseService service) { + BaseButtonlessDfuImpl(@NonNull final Intent intent, @NonNull final DfuBaseService service) { super(intent, service); } @@ -68,7 +70,8 @@ import android.content.Intent; * @param forceRefresh true, if cache should be cleared even for a bonded device. Usually the Service Changed indication should be used for this purpose. * @param scanForBootloader true to scan for advertising bootloader, false to keep the same address */ - protected void finalize(final Intent intent, final boolean forceRefresh, final boolean scanForBootloader) { + @SuppressWarnings("SameParameterValue") + void finalize(@NonNull final Intent intent, final boolean forceRefresh, final boolean scanForBootloader) { /* * We are done with DFU. Now the service may refresh device cache and clear stored services. * For bonded device this is required only if if doesn't support Service Changed indication. diff --git a/dfu/src/main/java/no/nordicsemi/android/dfu/BaseCustomDfuImpl.java b/dfu/src/main/java/no/nordicsemi/android/dfu/BaseCustomDfuImpl.java index 3b8ca39..dabe2f7 100644 --- a/dfu/src/main/java/no/nordicsemi/android/dfu/BaseCustomDfuImpl.java +++ b/dfu/src/main/java/no/nordicsemi/android/dfu/BaseCustomDfuImpl.java @@ -34,6 +34,7 @@ import java.io.IOException; import java.util.UUID; import java.util.zip.CRC32; +import androidx.annotation.NonNull; import no.nordicsemi.android.dfu.internal.exception.DeviceDisconnectedException; import no.nordicsemi.android.dfu.internal.exception.DfuException; import no.nordicsemi.android.dfu.internal.exception.HexFileValidationException; @@ -47,38 +48,43 @@ import no.nordicsemi.android.dfu.internal.exception.UploadAbortedException; /** * Flag indicating whether the firmware is being transmitted or not. */ - protected boolean mFirmwareUploadInProgress; + boolean mFirmwareUploadInProgress; /** - * The number of packets of firmware data to be send before receiving a new Packets receipt notification. 0 disables the packets notifications. + * The number of packets of firmware data to be send before receiving a new Packets + * receipt notification. 0 disables the packets notifications. */ - protected int mPacketsBeforeNotification; + int mPacketsBeforeNotification; /** * The number of packets sent since last notification. */ - protected int mPacketsSentSinceNotification; + private int mPacketsSentSinceNotification; /** *

    - * Flag set to true when the DFU target had send a notification with status other than success. Setting it to true will abort sending firmware and + * Flag set to true when the DFU target had send a notification with status other + * than success. Setting it to true will abort sending firmware and * stop logging notifications (read below for explanation). - *

    *

    - * The onCharacteristicWrite(..) callback is called when Android writes the packet into the outgoing queue, not when it physically sends the data. - * This means that the service will first put up to N* packets, one by one, to the queue, while in fact the first one is transmitted. - * In case the DFU target is in an invalid state it will notify Android with a notification 10-03-02 for each packet of firmware that has been sent. - * After receiving the first such notification, the DFU service will add the reset command to the outgoing queue, but it will still be receiving such notifications - * until all the data packets are sent. Those notifications should be ignored. This flag will prevent from logging "Notification received..." more than once. - *

    + * The onCharacteristicWrite(..) callback is called when Android writes the packet into the + * outgoing queue, not when it physically sends the data. This means that the service will + * first put up to N* packets, one by one, to the queue, while in fact the first one is transmitted. + * In case the DFU target is in an invalid state it will notify Android with a notification + * 10-03-02 for each packet of firmware that has been sent. After receiving the first such + * notification, the DFU service will add the reset command to the outgoing queue, + * but it will still be receiving such notifications until all the data packets are sent. + * Those notifications should be ignored. This flag will prevent from logging + * "Notification received..." more than once. *

    - * Additionally, sometimes after writing the command 6 ({@link LegacyDfuImpl#OP_CODE_RESET}), Android will receive a notification and update the characteristic value with 10-03-02 and the callback for write - * reset command will log "[DFU] Data written to ..., value (0x): 10-03-02" instead of "...(x0): 06". But this does not matter for the DFU process. - *

    + * Additionally, sometimes after writing the command 6 ({@link LegacyDfuImpl#OP_CODE_RESET}), + * Android will receive a notification and update the characteristic value with 10-03-02 and + * the callback for write reset command will log + * "[DFU] Data written to ..., value (0x): 10-03-02" instead of "...(x0): 06". + * But this does not matter for the DFU process. *

    * N* - Value of Packet Receipt Notification, 12 by default. - *

    */ - protected boolean mRemoteErrorOccurred; + boolean mRemoteErrorOccurred; - protected class BaseCustomBluetoothCallback extends BaseBluetoothGattCallback { + class BaseCustomBluetoothCallback extends BaseBluetoothGattCallback { protected void onPacketCharacteristicWrite(final BluetoothGatt gatt, final BluetoothGattCharacteristic characteristic, final int status) { // this method can be overwritten on the final class } @@ -88,7 +94,8 @@ import no.nordicsemi.android.dfu.internal.exception.UploadAbortedException; if (status == BluetoothGatt.GATT_SUCCESS) { /* * This method is called when either a CONTROL POINT or PACKET characteristic has been written. - * If it is the CONTROL POINT characteristic, just set the {@link mRequestCompleted} flag to true. The main thread will continue its task when notified. + * If it is the CONTROL POINT characteristic, just set the {@link mRequestCompleted} + * flag to true. The main thread will continue its task when notified. * If the PACKET characteristic was written we must: * - if the image size was written in DFU Start procedure, just set flag to true * otherwise @@ -98,7 +105,8 @@ import no.nordicsemi.android.dfu.internal.exception.UploadAbortedException; if (characteristic.getUuid().equals(getPacketCharacteristicUUID())) { if (mInitPacketInProgress) { // We've got confirmation that the init packet was sent - mService.sendLogBroadcast(DfuBaseService.LOG_LEVEL_INFO, "Data written to " + characteristic.getUuid() + ", value (0x): " + parse(characteristic)); + mService.sendLogBroadcast(DfuBaseService.LOG_LEVEL_INFO, + "Data written to " + characteristic.getUuid() + ", value (0x): " + parse(characteristic)); mInitPacketInProgress = false; } else if (mFirmwareUploadInProgress) { // If the PACKET characteristic was written with image data, update counters @@ -152,14 +160,18 @@ import no.nordicsemi.android.dfu.internal.exception.UploadAbortedException; onPacketCharacteristicWrite(gatt, characteristic, status); } } else { - // If the CONTROL POINT characteristic was written just set the flag to true. The main thread will continue its task when notified. - mService.sendLogBroadcast(DfuBaseService.LOG_LEVEL_INFO, "Data written to " + characteristic.getUuid() + ", value (0x): " + parse(characteristic)); + // If the CONTROL POINT characteristic was written just set the flag to true. + // The main thread will continue its task when notified. + mService.sendLogBroadcast(DfuBaseService.LOG_LEVEL_INFO, + "Data written to " + characteristic.getUuid() + ", value (0x): " + parse(characteristic)); mRequestCompleted = true; } } else { /* - * If a Reset (Op Code = 6) or Activate and Reset (Op Code = 5) commands are sent, the DFU target resets and sometimes does it so quickly that does not manage to send - * any ACK to the controller and error 133 is thrown here. This bug should be fixed in SDK 8.0+ where the target would gracefully disconnect before restarting. + * If a Reset (Op Code = 6) or Activate and Reset (Op Code = 5) commands are sent, + * the DFU target resets and sometimes does it so quickly that does not manage to send + * any ACK to the controller and error 133 is thrown here. This bug should be fixed + * in SDK 8.0+ where the target would gracefully disconnect before restarting. */ if (mResetRequestSent) mRequestCompleted = true; @@ -171,7 +183,7 @@ import no.nordicsemi.android.dfu.internal.exception.UploadAbortedException; notifyLock(); } - protected void handlePacketReceiptNotification(final BluetoothGatt gatt, final BluetoothGattCharacteristic characteristic) { + void handlePacketReceiptNotification(final BluetoothGatt gatt, final BluetoothGattCharacteristic characteristic) { // Secure DFU: // When PRN is set to be received after the object is complete we don't want to send anything. First the object needs to be executed. if (!mFirmwareUploadInProgress) { @@ -179,7 +191,8 @@ import no.nordicsemi.android.dfu.internal.exception.UploadAbortedException; return; } - final BluetoothGattCharacteristic packetCharacteristic = gatt.getService(getDfuServiceUUID()).getCharacteristic(getPacketCharacteristicUUID()); + final BluetoothGattCharacteristic packetCharacteristic = + gatt.getService(getDfuServiceUUID()).getCharacteristic(getPacketCharacteristicUUID()); try { mPacketsSentSinceNotification = 0; @@ -216,19 +229,23 @@ import no.nordicsemi.android.dfu.internal.exception.UploadAbortedException; } } - protected void handleNotification(final BluetoothGatt gatt, final BluetoothGattCharacteristic characteristic) { - mService.sendLogBroadcast(DfuBaseService.LOG_LEVEL_INFO, "Notification received from " + characteristic.getUuid() + ", value (0x): " + parse(characteristic)); + @SuppressWarnings("unused") + void handleNotification(final BluetoothGatt gatt, final BluetoothGattCharacteristic characteristic) { + mService.sendLogBroadcast(DfuBaseService.LOG_LEVEL_INFO, + "Notification received from " + characteristic.getUuid() + ", value (0x): " + parse(characteristic)); mReceivedData = characteristic.getValue(); mFirmwareUploadInProgress = false; } } - BaseCustomDfuImpl(final Intent intent, final DfuBaseService service) { + @SuppressWarnings("deprecation") + BaseCustomDfuImpl(@NonNull final Intent intent, final DfuBaseService service) { super(intent, service); if (intent.hasExtra(DfuBaseService.EXTRA_PACKET_RECEIPT_NOTIFICATIONS_ENABLED)) { // Read from intent - final boolean packetReceiptNotificationEnabled = intent.getBooleanExtra(DfuBaseService.EXTRA_PACKET_RECEIPT_NOTIFICATIONS_ENABLED, Build.VERSION.SDK_INT < Build.VERSION_CODES.M); + final boolean packetReceiptNotificationEnabled = + intent.getBooleanExtra(DfuBaseService.EXTRA_PACKET_RECEIPT_NOTIFICATIONS_ENABLED, Build.VERSION.SDK_INT < Build.VERSION_CODES.M); int numberOfPackets = intent.getIntExtra(DfuBaseService.EXTRA_PACKET_RECEIPT_NOTIFICATIONS_VALUE, DfuServiceInitiator.DEFAULT_PRN_VALUE); if (numberOfPackets < 0 || numberOfPackets > 0xFFFF) numberOfPackets = DfuServiceInitiator.DEFAULT_PRN_VALUE; @@ -238,10 +255,12 @@ import no.nordicsemi.android.dfu.internal.exception.UploadAbortedException; } else { // Read preferences final SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(service); - final boolean packetReceiptNotificationEnabled = preferences.getBoolean(DfuSettingsConstants.SETTINGS_PACKET_RECEIPT_NOTIFICATION_ENABLED, Build.VERSION.SDK_INT < Build.VERSION_CODES.M); + final boolean packetReceiptNotificationEnabled = + preferences.getBoolean(DfuSettingsConstants.SETTINGS_PACKET_RECEIPT_NOTIFICATION_ENABLED, Build.VERSION.SDK_INT < Build.VERSION_CODES.M); String value = preferences.getString(DfuSettingsConstants.SETTINGS_NUMBER_OF_PACKETS, String.valueOf(DfuServiceInitiator.DEFAULT_PRN_VALUE)); int numberOfPackets; try { + //noinspection ConstantConditions numberOfPackets = Integer.parseInt(value); if (numberOfPackets < 0 || numberOfPackets > 0xFFFF) numberOfPackets = DfuServiceInitiator.DEFAULT_PRN_VALUE; @@ -262,13 +281,16 @@ import no.nordicsemi.android.dfu.internal.exception.UploadAbortedException; /** * Wends the whole init packet stream to the given characteristic. + * * @param characteristic the target characteristic * @param crc32 the CRC object to be updated based on the data sent - * @throws DfuException - * @throws DeviceDisconnectedException - * @throws UploadAbortedException + * @throws DeviceDisconnectedException Thrown when the device will disconnect in the middle of + * the transmission. + * @throws DfuException Thrown if DFU error occur. + * @throws UploadAbortedException Thrown if DFU operation was aborted by user. */ - protected void writeInitData(final BluetoothGattCharacteristic characteristic, final CRC32 crc32) throws DfuException, DeviceDisconnectedException, UploadAbortedException { + void writeInitData(final BluetoothGattCharacteristic characteristic, final CRC32 crc32) + throws DfuException, DeviceDisconnectedException, UploadAbortedException { try { byte[] data = mBuffer; int size; @@ -284,18 +306,20 @@ import no.nordicsemi.android.dfu.internal.exception.UploadAbortedException; } /** - * Writes the Init packet to the characteristic. This method is SYNCHRONOUS and wait until the {@link android.bluetooth.BluetoothGattCallback#onCharacteristicWrite(android.bluetooth.BluetoothGatt, android.bluetooth.BluetoothGattCharacteristic, int)} - * will be called or the device gets disconnected. If connection state will change, or an error will occur, an exception will be thrown. + * Writes the Init packet to the characteristic. This method is SYNCHRONOUS and wait until the + * {@link android.bluetooth.BluetoothGattCallback#onCharacteristicWrite(android.bluetooth.BluetoothGatt, android.bluetooth.BluetoothGattCharacteristic, int)} + * will be called or the device gets disconnected. If connection state will change, + * or an error will occur, an exception will be thrown. * - * @param characteristic the characteristic to write to. Should be the DFU PACKET - * @param buffer the init packet as a byte array. This must be shorter or equal to 20 bytes (TODO check this restriction). - * @param size the init packet size - * @throws DeviceDisconnectedException - * @throws DfuException - * @throws UploadAbortedException + * @param characteristic the characteristic to write to. Should be the DFU PACKET. + * @param buffer the init packet as a byte array. + * @param size the init packet size. + * @throws DeviceDisconnectedException Thrown when the device will disconnect in the middle of the transmission. + * @throws DfuException Thrown if DFU error occur. + * @throws UploadAbortedException Thrown if DFU operation was aborted by user. */ - private void writeInitPacket(final BluetoothGattCharacteristic characteristic, final byte[] buffer, final int size) throws DeviceDisconnectedException, DfuException, - UploadAbortedException { + private void writeInitPacket(final BluetoothGattCharacteristic characteristic, final byte[] buffer, final int size) + throws DeviceDisconnectedException, DfuException, UploadAbortedException { if (mAborted) throw new UploadAbortedException(); byte[] locBuffer = buffer; @@ -330,16 +354,18 @@ import no.nordicsemi.android.dfu.internal.exception.UploadAbortedException; } /** - * Starts sending the data. This method is SYNCHRONOUS and terminates when the whole file will be uploaded or the device get disconnected. - * If connection state will change, or an error will occur, an exception will be thrown. + * Starts sending the data. This method is SYNCHRONOUS and terminates when the whole file will + * be uploaded or the device get disconnected. If connection state will change, or an error + * will occur, an exception will be thrown. * - * @param packetCharacteristic the characteristic to write file content to. Must be the DFU PACKET - * @throws DeviceDisconnectedException Thrown when the device will disconnect in the middle of the transmission. - * @throws DfuException Thrown if DFU error occur - * @throws UploadAbortedException + * @param packetCharacteristic the characteristic to write file content to. Must be the DFU PACKET. + * @throws DeviceDisconnectedException Thrown when the device will disconnect in the middle + * of the transmission. + * @throws DfuException Thrown if DFU error occur. + * @throws UploadAbortedException Thrown if DFU operation was aborted by user. */ - protected void uploadFirmwareImage(final BluetoothGattCharacteristic packetCharacteristic) throws DeviceDisconnectedException, - DfuException, UploadAbortedException { + void uploadFirmwareImage(final BluetoothGattCharacteristic packetCharacteristic) + throws DeviceDisconnectedException, DfuException, UploadAbortedException { if (mAborted) throw new UploadAbortedException(); mReceivedData = null; @@ -350,7 +376,8 @@ import no.nordicsemi.android.dfu.internal.exception.UploadAbortedException; final byte[] buffer = mBuffer; try { final int size = mFirmwareStream.read(buffer); - mService.sendLogBroadcast(DfuBaseService.LOG_LEVEL_VERBOSE, "Sending firmware to characteristic " + packetCharacteristic.getUuid() + "..."); + mService.sendLogBroadcast(DfuBaseService.LOG_LEVEL_VERBOSE, + "Sending firmware to characteristic " + packetCharacteristic.getUuid() + "..."); writePacket(mGatt, packetCharacteristic, buffer, size); } catch (final HexFileValidationException e) { throw new DfuException("HEX file not valid", DfuBaseService.ERROR_FILE_INVALID); @@ -374,11 +401,12 @@ import no.nordicsemi.android.dfu.internal.exception.UploadAbortedException; } /** - * Writes the buffer to the characteristic. The maximum size of the buffer is 20 bytes. This method is ASYNCHRONOUS and returns immediately after adding the data to TX queue. + * Writes the buffer to the characteristic. The maximum size of the buffer is dependent on MTU. + * This method is ASYNCHRONOUS and returns immediately after adding the data to TX queue. * - * @param characteristic the characteristic to write to. Should be the DFU PACKET - * @param buffer the buffer with 1-20 bytes - * @param size the number of bytes from the buffer to send + * @param characteristic the characteristic to write to. Should be the DFU PACKET. + * @param buffer the buffer with 1-20 bytes. + * @param size the number of bytes from the buffer to send. */ private void writePacket(final BluetoothGatt gatt, final BluetoothGattCharacteristic characteristic, final byte[] buffer, final int size) { byte[] locBuffer = buffer; @@ -391,25 +419,24 @@ import no.nordicsemi.android.dfu.internal.exception.UploadAbortedException; characteristic.setWriteType(BluetoothGattCharacteristic.WRITE_TYPE_NO_RESPONSE); characteristic.setValue(locBuffer); gatt.writeCharacteristic(characteristic); - // FIXME BLE buffer overflow - // after writing to the device with WRITE_NO_RESPONSE property the onCharacteristicWrite callback is received immediately after writing data to a buffer. - // The real sending is much slower than adding to the buffer. This method does not return false if writing didn't succeed.. just the callback is not invoked. - // - // More info: this works fine on Nexus 5 (Android 4.4) (4.3 seconds) and on Samsung S4 (Android 4.3) (20 seconds) so this is a driver issue. - // Nexus 4 and 7 uses Qualcomm chip, Nexus 5 and Samsung uses Broadcom chips. } /** - * Closes the BLE connection to the device and removes/restores bonding, if a proper flags were set in the {@link DfuServiceInitiator}. - * This method will also change the DFU state to completed or restart the service to send the second part. - * @param intent the intent used to start the DFU service. It contains all user flags in the bundle. - * @param forceRefresh true, if cache should be cleared even for a bonded device. Usually the Service Changed indication should be used for this purpose. + * Closes the BLE connection to the device and removes/restores bonding, if a proper flags were + * set in the {@link DfuServiceInitiator}. This method will also change the DFU state to + * completed or restart the service to send the second part. + * + * @param intent the intent used to start the DFU service. + * It contains all user flags in the bundle. + * @param forceRefresh true, if cache should be cleared even for a bonded device. + * Usually the Service Changed indication should be used for this purpose. */ - protected void finalize(final Intent intent, final boolean forceRefresh) { + void finalize(final Intent intent, final boolean forceRefresh) { /* * We are done with DFU. Now the service may refresh device cache and clear stored services. * For bonded device this is required only if if doesn't support Service Changed indication. - * Android shouldn't cache services of non-bonded devices having Service Changed characteristic in their database, but it does, so... + * Android shouldn't cache services of non-bonded devices having Service Changed + * characteristic in their database, but it does, so... */ final boolean keepBond = intent.getBooleanExtra(DfuBaseService.EXTRA_KEEP_BOND, false); mService.refreshDeviceCache(mGatt, forceRefresh || !keepBond); @@ -429,7 +456,8 @@ import no.nordicsemi.android.dfu.internal.exception.UploadAbortedException; // The bond information was lost. removeBond(); - // Give some time for removing the bond information. 300ms was to short, let's set it to 2 seconds just to be sure. + // Give some time for removing the bond information. 300 ms was to short, + // let's set it to 2 seconds just to be sure. mService.waitFor(2000); alreadyWaited = true; } @@ -443,8 +471,10 @@ import no.nordicsemi.android.dfu.internal.exception.UploadAbortedException; /* * We need to send PROGRESS_COMPLETED message only when all files has been transmitted. - * In case you want to send the Soft Device and/or Bootloader and the Application, the service will be started twice: one to send SD+BL, and the - * second time to send the Application only (using the new Bootloader). In the first case we do not send PROGRESS_COMPLETED notification. + * In case you want to send the Soft Device and/or Bootloader and the Application, + * the service will be started twice: one to send SD+BL, and the second time to send the + * Application only (using the new Bootloader). + * In the first case we do not send PROGRESS_COMPLETED notification. */ if (mProgressInfo.isLastPart()) { // Delay this event a little bit. Android needs some time to prepare for reconnection. @@ -453,17 +483,24 @@ import no.nordicsemi.android.dfu.internal.exception.UploadAbortedException; mProgressInfo.setProgress(DfuBaseService.PROGRESS_COMPLETED); } else { /* - * In case when the SoftDevice has been upgraded, and the application should be send in the following connection, we have to - * make sure that we know the address the device is advertising with. Depending on the method used to start the DFU bootloader the first time + * In case when the SoftDevice has been upgraded, and the application should be send + * in the following connection, we have to make sure that we know the address the device + * is advertising with. Depending on the method used to start the DFU bootloader the first time * the new Bootloader may advertise with the same address or one incremented by 1. - * When the buttonless update was used, the bootloader will use the same address as the application. The cached list of services on the Android device - * should be cleared thanks to the Service Changed characteristic (the fact that it exists if not bonded, or the Service Changed indication on bonded one). - * In case of forced DFU mode (using a button), the Bootloader does not know whether there was the Service Changed characteristic present in the list of - * application's services so it must advertise with a different address. The same situation applies when the new Soft Device was uploaded and the old - * application has been removed in this process. + * When the buttonless update was used, the bootloader will use the same address as the + * application. The cached list of services on the Android device should be cleared + * thanks to the Service Changed characteristic (the fact that it exists if not bonded, + * or the Service Changed indication on bonded one). + * In case of forced DFU mode (using a button), the Bootloader does not know whether + * there was the Service Changed characteristic present in the list of application's + * services so it must advertise with a different address. The same situation applies + * when the new Soft Device was uploaded and the old application has been removed in + * this process. * - * We could have save the fact of jumping as a parameter of the service but it ma be that some Android devices must first scan a device before connecting to it. - * It a device with the address+1 has never been detected before the service could have failed on connection. + * We could have save the fact of jumping as a parameter of the service but it ma be + * that some Android devices must first scan a device before connecting to it. + * It a device with the address+1 has never been detected before the service could have + * failed on connection. */ /* diff --git a/dfu/src/main/java/no/nordicsemi/android/dfu/BaseDfuImpl.java b/dfu/src/main/java/no/nordicsemi/android/dfu/BaseDfuImpl.java index a5788af..9d9ef96 100644 --- a/dfu/src/main/java/no/nordicsemi/android/dfu/BaseDfuImpl.java +++ b/dfu/src/main/java/no/nordicsemi/android/dfu/BaseDfuImpl.java @@ -22,7 +22,6 @@ package no.nordicsemi.android.dfu; -import android.annotation.SuppressLint; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothGatt; import android.bluetooth.BluetoothGattCharacteristic; @@ -30,13 +29,16 @@ import android.bluetooth.BluetoothGattDescriptor; import android.bluetooth.BluetoothGattService; import android.content.Intent; import android.os.Build; -import androidx.annotation.RequiresApi; import android.util.Log; import java.io.InputStream; import java.lang.reflect.Method; import java.util.UUID; +import androidx.annotation.IntRange; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.annotation.RequiresApi; import no.nordicsemi.android.dfu.internal.ArchiveInputStream; import no.nordicsemi.android.dfu.internal.exception.DeviceDisconnectedException; import no.nordicsemi.android.dfu.internal.exception.DfuException; @@ -46,59 +48,72 @@ import no.nordicsemi.android.dfu.internal.scanner.BootloaderScannerFactory; /* package */ abstract class BaseDfuImpl implements DfuService { private static final String TAG = "DfuImpl"; - protected static final UUID GENERIC_ATTRIBUTE_SERVICE_UUID = new UUID(0x0000180100001000L, 0x800000805F9B34FBL); - protected static final UUID SERVICE_CHANGED_UUID = new UUID(0x00002A0500001000L, 0x800000805F9B34FBL); - protected static final UUID CLIENT_CHARACTERISTIC_CONFIG = new UUID(0x0000290200001000L, 0x800000805f9b34fbL); - protected static final int NOTIFICATIONS = 1; - protected static final int INDICATIONS = 2; + static final UUID GENERIC_ATTRIBUTE_SERVICE_UUID = new UUID(0x0000180100001000L, 0x800000805F9B34FBL); + static final UUID SERVICE_CHANGED_UUID = new UUID(0x00002A0500001000L, 0x800000805F9B34FBL); + static final UUID CLIENT_CHARACTERISTIC_CONFIG = new UUID(0x0000290200001000L, 0x800000805f9b34fbL); + static final int NOTIFICATIONS = 1; + static final int INDICATIONS = 2; - protected static final char[] HEX_ARRAY = "0123456789ABCDEF".toCharArray(); - protected static final int MAX_PACKET_SIZE_DEFAULT = 20; // the default maximum number of bytes in one packet is 20. + private static final char[] HEX_ARRAY = "0123456789ABCDEF".toCharArray(); + private static final int MAX_PACKET_SIZE_DEFAULT = 20; // the default maximum number of bytes in one packet is 20. /** - * Lock used in synchronization purposes + * Lock used in synchronization purposes. */ - protected final Object mLock = new Object(); + final Object mLock = new Object(); - protected InputStream mFirmwareStream; - protected InputStream mInitPacketStream; + InputStream mFirmwareStream; + InputStream mInitPacketStream; - /** The target GATT device. */ - protected BluetoothGatt mGatt; - /** The firmware type. See TYPE_* constants. */ - protected int mFileType; - /** Flag set to true if sending was paused. */ - protected boolean mPaused; - /** Flag set to true if sending was aborted. */ - protected boolean mAborted; - /** Flag indicating whether the device is still connected. */ - protected boolean mConnected; + /** + * The target GATT device. + */ + BluetoothGatt mGatt; + /** + * The firmware type. See TYPE_* constants. + */ + int mFileType; + /** + * Flag set to true if sending was paused. + */ + boolean mPaused; + /** + * Flag set to true if sending was aborted. + */ + boolean mAborted; + /** + * Flag indicating whether the device is still connected. + */ + boolean mConnected; /** * Flag indicating whether the request was completed or not */ - protected boolean mRequestCompleted; + boolean mRequestCompleted; /** - * Flag sent when a request has been sent that will cause the DFU target to reset. Often, after sending such command, Android throws a connection state error. If this flag is set the error will be - * ignored. + * Flag sent when a request has been sent that will cause the DFU target to reset. + * Often, after sending such command, Android throws a connection state error. + * If this flag is set the error will be ignored. */ - protected boolean mResetRequestSent; + boolean mResetRequestSent; /** - * The number of the last error that has occurred or 0 if there was no error + * The number of the last error that has occurred or 0 if there was no error. */ - protected int mError; + int mError; /** * Latest data received from device using notification. */ - protected byte[] mReceivedData = null; - protected byte[] mBuffer = new byte[MAX_PACKET_SIZE_DEFAULT]; - protected DfuBaseService mService; - protected DfuProgressInfo mProgressInfo; - protected int mImageSizeInBytes; - protected int mInitPacketSizeInBytes; + byte[] mReceivedData = null; + byte[] mBuffer = new byte[MAX_PACKET_SIZE_DEFAULT]; + DfuBaseService mService; + DfuProgressInfo mProgressInfo; + int mImageSizeInBytes; + int mInitPacketSizeInBytes; private int mCurrentMtu; protected class BaseBluetoothGattCallback extends DfuGattCallback { - // The Implementation object is created depending on device services, so after the device is connected and services were scanned. + // The Implementation object is created depending on device services, so after the device + // is connected and services were scanned. + // public void onConnected() { } @Override @@ -113,7 +128,8 @@ import no.nordicsemi.android.dfu.internal.scanner.BootloaderScannerFactory; /* * This method is called when the DFU Version characteristic has been read. */ - mService.sendLogBroadcast(DfuBaseService.LOG_LEVEL_INFO, "Read Response received from " + characteristic.getUuid() + ", value (0x): " + parse(characteristic)); + mService.sendLogBroadcast(DfuBaseService.LOG_LEVEL_INFO, + "Read Response received from " + characteristic.getUuid() + ", value (0x): " + parse(characteristic)); mReceivedData = characteristic.getValue(); mRequestCompleted = true; } else { @@ -127,7 +143,8 @@ import no.nordicsemi.android.dfu.internal.scanner.BootloaderScannerFactory; public void onDescriptorRead(final BluetoothGatt gatt, final BluetoothGattDescriptor descriptor, final int status) { if (status == BluetoothGatt.GATT_SUCCESS) { if (CLIENT_CHARACTERISTIC_CONFIG.equals(descriptor.getUuid())) { - mService.sendLogBroadcast(DfuBaseService.LOG_LEVEL_INFO, "Read Response received from descr." + descriptor.getCharacteristic().getUuid() + ", value (0x): " + parse(descriptor)); + mService.sendLogBroadcast(DfuBaseService.LOG_LEVEL_INFO, + "Read Response received from descr." + descriptor.getCharacteristic().getUuid() + ", value (0x): " + parse(descriptor)); if (SERVICE_CHANGED_UUID.equals(descriptor.getCharacteristic().getUuid())) { // We have enabled indications for the Service Changed characteristic mRequestCompleted = true; @@ -147,13 +164,16 @@ import no.nordicsemi.android.dfu.internal.scanner.BootloaderScannerFactory; public void onDescriptorWrite(final BluetoothGatt gatt, final BluetoothGattDescriptor descriptor, final int status) { if (status == BluetoothGatt.GATT_SUCCESS) { if (CLIENT_CHARACTERISTIC_CONFIG.equals(descriptor.getUuid())) { - mService.sendLogBroadcast(DfuBaseService.LOG_LEVEL_INFO, "Data written to descr." + descriptor.getCharacteristic().getUuid() + ", value (0x): " + parse(descriptor)); + mService.sendLogBroadcast(DfuBaseService.LOG_LEVEL_INFO, + "Data written to descr." + descriptor.getCharacteristic().getUuid() + ", value (0x): " + parse(descriptor)); if (SERVICE_CHANGED_UUID.equals(descriptor.getCharacteristic().getUuid())) { // We have enabled indications for the Service Changed characteristic - mService.sendLogBroadcast(DfuBaseService.LOG_LEVEL_VERBOSE, "Indications enabled for " + descriptor.getCharacteristic().getUuid()); + mService.sendLogBroadcast(DfuBaseService.LOG_LEVEL_VERBOSE, + "Indications enabled for " + descriptor.getCharacteristic().getUuid()); } else { // We have enabled notifications for this characteristic - mService.sendLogBroadcast(DfuBaseService.LOG_LEVEL_VERBOSE, "Notifications enabled for " + descriptor.getCharacteristic().getUuid()); + mService.sendLogBroadcast(DfuBaseService.LOG_LEVEL_VERBOSE, + "Notifications enabled for " + descriptor.getCharacteristic().getUuid()); } } } else { @@ -165,7 +185,7 @@ import no.nordicsemi.android.dfu.internal.scanner.BootloaderScannerFactory; @Override public void onMtuChanged(final BluetoothGatt gatt, final int mtu, final int status) { - if (status == BluetoothGatt.GATT_SUCCESS) { + if (status == BluetoothGatt.GATT_SUCCESS) { mService.sendLogBroadcast(DfuBaseService.LOG_LEVEL_INFO, "MTU changed to: " + mtu); if (mtu - 3 > mBuffer.length) mBuffer = new byte[mtu - 3]; // Maximum payload size is MTU - 3 bytes @@ -183,8 +203,9 @@ import no.nordicsemi.android.dfu.internal.scanner.BootloaderScannerFactory; @Override public void onPhyUpdate(final BluetoothGatt gatt, final int txPhy, final int rxPhy, final int status) { - if (status == BluetoothGatt.GATT_SUCCESS) { - mService.sendLogBroadcast(DfuBaseService.LOG_LEVEL_INFO, "PHY updated (TX: " + phyToString(txPhy) + ", RX: " + phyToString(rxPhy) + ")"); + if (status == BluetoothGatt.GATT_SUCCESS) { + mService.sendLogBroadcast(DfuBaseService.LOG_LEVEL_INFO, + "PHY updated (TX: " + phyToString(txPhy) + ", RX: " + phyToString(rxPhy) + ")"); logi("PHY updated (TX: " + phyToString(txPhy) + ", RX: " + phyToString(rxPhy) + ")"); } else { logw("Updating PHY failed: " + status + " (txPhy: " + txPhy + ", rxPhy: " + rxPhy + ")"); @@ -231,7 +252,8 @@ import no.nordicsemi.android.dfu.internal.scanner.BootloaderScannerFactory; } } - /* package */ BaseDfuImpl(final Intent intent, final DfuBaseService service) { + @SuppressWarnings("unused") + BaseDfuImpl(@NonNull final Intent intent, @NonNull final DfuBaseService service) { mService = service; mProgressInfo = service.mProgressInfo; mConnected = true; // the device is connected when impl object it created @@ -267,7 +289,11 @@ import no.nordicsemi.android.dfu.internal.scanner.BootloaderScannerFactory; } @Override - public boolean initialize(final Intent intent, final BluetoothGatt gatt, final int fileType, final InputStream firmwareStream, final InputStream initPacketStream) throws DfuException, DeviceDisconnectedException, UploadAbortedException { + public boolean initialize(@NonNull final Intent intent, @NonNull final BluetoothGatt gatt, + final int fileType, + @NonNull final InputStream firmwareStream, + @Nullable final InputStream initPacketStream) + throws DfuException, DeviceDisconnectedException, UploadAbortedException { mGatt = gatt; mFileType = fileType; mFirmwareStream = firmwareStream; @@ -293,14 +319,16 @@ import no.nordicsemi.android.dfu.internal.scanner.BootloaderScannerFactory; mService.sendLogBroadcast(DfuBaseService.LOG_LEVEL_WARNING, "Sending application"); } - int size; + int size = 0; try { - if (initPacketStream.markSupported()) { - initPacketStream.reset(); - } - size = initPacketStream.available(); + if (initPacketStream != null) { + if (initPacketStream.markSupported()) { + initPacketStream.reset(); + } + size = initPacketStream.available(); + } } catch (final Exception e) { - size = 0; + // ignore } mInitPacketSizeInBytes = size; try { @@ -350,14 +378,14 @@ import no.nordicsemi.android.dfu.internal.scanner.BootloaderScannerFactory; return true; } - protected void notifyLock() { + void notifyLock() { // Notify waiting thread synchronized (mLock) { mLock.notifyAll(); } } - protected void waitIfPaused() { + void waitIfPaused() { try { synchronized (mLock) { while (mPaused) @@ -369,16 +397,21 @@ import no.nordicsemi.android.dfu.internal.scanner.BootloaderScannerFactory; } /** - * Enables or disables the notifications for given characteristic. This method is SYNCHRONOUS and wait until the - * {@link android.bluetooth.BluetoothGattCallback#onDescriptorWrite(android.bluetooth.BluetoothGatt, android.bluetooth.BluetoothGattDescriptor, int)} will be called or the device gets disconnected. + * Enables or disables the notifications for given characteristic. + * This method is SYNCHRONOUS and wait until the + * {@link android.bluetooth.BluetoothGattCallback#onDescriptorWrite(android.bluetooth.BluetoothGatt, android.bluetooth.BluetoothGattDescriptor, int)} + * will be called or the device gets disconnected. * If connection state will change, or an error will occur, an exception will be thrown. * - * @param characteristic the characteristic to enable or disable notifications for - * @param type {@link #NOTIFICATIONS} or {@link #INDICATIONS} - * @throws DfuException - * @throws UploadAbortedException + * @param characteristic the characteristic to enable or disable notifications for. + * @param type {@link #NOTIFICATIONS} or {@link #INDICATIONS}. + * @throws DeviceDisconnectedException Thrown when the device will disconnect in the middle of + * the transmission. + * @throws DfuException Thrown if DFU error occur. + * @throws UploadAbortedException Thrown if DFU operation was aborted by user. */ - protected void enableCCCD(final BluetoothGattCharacteristic characteristic, final int type) throws DeviceDisconnectedException, DfuException, UploadAbortedException { + void enableCCCD(@NonNull final BluetoothGattCharacteristic characteristic, final int type) + throws DeviceDisconnectedException, DfuException, UploadAbortedException { final BluetoothGatt gatt = mGatt; final String debugString = type == NOTIFICATIONS ? "notifications" : "indications"; if (!mConnected) @@ -394,15 +427,18 @@ import no.nordicsemi.android.dfu.internal.scanner.BootloaderScannerFactory; return; logi("Enabling " + debugString + "..."); - mService.sendLogBroadcast(DfuBaseService.LOG_LEVEL_VERBOSE, "Enabling " + debugString + " for " + characteristic.getUuid()); + mService.sendLogBroadcast(DfuBaseService.LOG_LEVEL_VERBOSE, + "Enabling " + debugString + " for " + characteristic.getUuid()); // enable notifications locally - mService.sendLogBroadcast(DfuBaseService.LOG_LEVEL_DEBUG, "gatt.setCharacteristicNotification(" + characteristic.getUuid() + ", true)"); + mService.sendLogBroadcast(DfuBaseService.LOG_LEVEL_DEBUG, + "gatt.setCharacteristicNotification(" + characteristic.getUuid() + ", true)"); gatt.setCharacteristicNotification(characteristic, true); // enable notifications on the device descriptor.setValue(type == NOTIFICATIONS ? BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE : BluetoothGattDescriptor.ENABLE_INDICATION_VALUE); - mService.sendLogBroadcast(DfuBaseService.LOG_LEVEL_DEBUG, "gatt.writeDescriptor(" + descriptor.getUuid() + (type == NOTIFICATIONS ? ", value=0x01-00)" : ", value=0x02-00)")); + mService.sendLogBroadcast(DfuBaseService.LOG_LEVEL_DEBUG, + "gatt.writeDescriptor(" + descriptor.getUuid() + (type == NOTIFICATIONS ? ", value=0x01-00)" : ", value=0x02-00)")); gatt.writeDescriptor(descriptor); // We have to wait until device receives a response or an error occur @@ -426,12 +462,14 @@ import no.nordicsemi.android.dfu.internal.scanner.BootloaderScannerFactory; /** * Reads the value of the Service Changed Client Characteristic Configuration descriptor (CCCD). * - * @return true if Service Changed CCCD is enabled and set to INDICATE - * @throws DeviceDisconnectedException - * @throws DfuException - * @throws UploadAbortedException + * @return true if Service Changed CCCD is enabled and set to INDICATE. + * @throws DeviceDisconnectedException Thrown when the device will disconnect in the middle of + * the transmission. + * @throws DfuException Thrown if DFU error occur. + * @throws UploadAbortedException Thrown if DFU operation was aborted by user. */ - private boolean isServiceChangedCCCDEnabled() throws DeviceDisconnectedException, DfuException, UploadAbortedException { + private boolean isServiceChangedCCCDEnabled() + throws DeviceDisconnectedException, DfuException, UploadAbortedException { if (!mConnected) throw new DeviceDisconnectedException("Unable to read Service Changed CCCD: device disconnected"); if (mAborted) @@ -480,27 +518,32 @@ import no.nordicsemi.android.dfu.internal.scanner.BootloaderScannerFactory; } /** - * Writes the operation code to the characteristic. This method is SYNCHRONOUS and wait until the - * {@link android.bluetooth.BluetoothGattCallback#onCharacteristicWrite(android.bluetooth.BluetoothGatt, android.bluetooth.BluetoothGattCharacteristic, int)} will be called or - * the device gets disconnected. If connection state will change, or an error will occur, an exception will be thrown. + * Writes the operation code to the characteristic. + * This method is SYNCHRONOUS and wait until the + * {@link android.bluetooth.BluetoothGattCallback#onCharacteristicWrite(android.bluetooth.BluetoothGatt, android.bluetooth.BluetoothGattCharacteristic, int)} + * will be called or the device gets disconnected. + * If connection state will change, or an error will occur, an exception will be thrown. * * @param characteristic the characteristic to write to. Should be the DFU CONTROL POINT * @param value the value to write to the characteristic * @param reset whether the command trigger restarting the device - * @throws DeviceDisconnectedException - * @throws DfuException - * @throws UploadAbortedException + * @throws DeviceDisconnectedException Thrown when the device will disconnect in the middle of + * the transmission. + * @throws DfuException Thrown if DFU error occur. + * @throws UploadAbortedException Thrown if DFU operation was aborted by user. */ - protected void writeOpCode(final BluetoothGattCharacteristic characteristic, final byte[] value, final boolean reset) throws DeviceDisconnectedException, DfuException, - UploadAbortedException { + void writeOpCode(@NonNull final BluetoothGattCharacteristic characteristic, @NonNull final byte[] value, final boolean reset) + throws DeviceDisconnectedException, DfuException, UploadAbortedException { if (mAborted) throw new UploadAbortedException(); mReceivedData = null; mError = 0; mRequestCompleted = false; /* - * Sending a command that will make the DFU target to reboot may cause an error 133 (0x85 - Gatt Error). If so, with this flag set, the error will not be shown to the user - * as the peripheral is disconnected anyway. See: mGattCallback#onCharacteristicWrite(...) method + * Sending a command that will make the DFU target to reboot may cause an error 133 + * (0x85 - Gatt Error). If so, with this flag set, the error will not be shown to the user + * as the peripheral is disconnected anyway. + * See: mGattCallback#onCharacteristicWrite(...) method */ mResetRequestSent = reset; @@ -530,8 +573,8 @@ import no.nordicsemi.android.dfu.internal.scanner.BootloaderScannerFactory; * * @return true if it's already bonded or the bonding has started */ - @SuppressLint("NewApi") - protected boolean createBond() { + @SuppressWarnings("UnusedReturnValue") + boolean createBond() { final BluetoothDevice device = mGatt.getDevice(); if (device.getBondState() == BluetoothDevice.BOND_BONDED) return true; @@ -565,16 +608,14 @@ import no.nordicsemi.android.dfu.internal.scanner.BootloaderScannerFactory; * @param device the target device * @return false if bonding failed (no hidden createBond() method in BluetoothDevice, or this method returned false */ - private boolean createBondApi18(final BluetoothDevice device) { + private boolean createBondApi18(@NonNull final BluetoothDevice device) { /* * There is a createBond() method in BluetoothDevice class but for now it's hidden. We will call it using reflections. It has been revealed in KitKat (Api19) */ try { final Method createBond = device.getClass().getMethod("createBond"); - if (createBond != null) { - mService.sendLogBroadcast(DfuBaseService.LOG_LEVEL_DEBUG, "gatt.getDevice().createBond() (hidden)"); - return (Boolean) createBond.invoke(device); - } + mService.sendLogBroadcast(DfuBaseService.LOG_LEVEL_DEBUG, "gatt.getDevice().createBond() (hidden)"); + return (Boolean) createBond.invoke(device); } catch (final Exception e) { Log.w(TAG, "An exception occurred while creating bond", e); } @@ -584,9 +625,10 @@ import no.nordicsemi.android.dfu.internal.scanner.BootloaderScannerFactory; /** * Removes the bond information for the given device. * - * @return true if operation succeeded, false otherwise + * @return true if operation succeeded, false otherwise */ - protected boolean removeBond() { + @SuppressWarnings("UnusedReturnValue") + boolean removeBond() { final BluetoothDevice device = mGatt.getDevice(); if (device.getBondState() == BluetoothDevice.BOND_NONE) return true; @@ -597,23 +639,21 @@ import no.nordicsemi.android.dfu.internal.scanner.BootloaderScannerFactory; * There is a removeBond() method in BluetoothDevice class but for now it's hidden. We will call it using reflections. */ try { - final Method removeBond = device.getClass().getMethod("removeBond"); - if (removeBond != null) { - mRequestCompleted = false; - mService.sendLogBroadcast(DfuBaseService.LOG_LEVEL_DEBUG, "gatt.getDevice().removeBond() (hidden)"); - result = (Boolean) removeBond.invoke(device); + //noinspection JavaReflectionMemberAccess + final Method removeBond = device.getClass().getMethod("removeBond"); + mRequestCompleted = false; + mService.sendLogBroadcast(DfuBaseService.LOG_LEVEL_DEBUG, "gatt.getDevice().removeBond() (hidden)"); + result = (Boolean) removeBond.invoke(device); - // We have to wait until device is unbounded - try { - synchronized (mLock) { - while (!mRequestCompleted && !mAborted) - mLock.wait(); - } - } catch (final InterruptedException e) { - loge("Sleeping interrupted", e); - } - } - result = true; + // We have to wait until device is unbounded + try { + synchronized (mLock) { + while (!mRequestCompleted && !mAborted) + mLock.wait(); + } + } catch (final InterruptedException e) { + loge("Sleeping interrupted", e); + } } catch (final Exception e) { Log.w(TAG, "An exception occurred while removing bond information", e); } @@ -622,9 +662,10 @@ import no.nordicsemi.android.dfu.internal.scanner.BootloaderScannerFactory; /** * Returns whether the device is bonded. + * * @return true if the device is bonded, false if not bonded or in process of bonding. */ - protected boolean isBonded() { + boolean isBonded() { final BluetoothDevice device = mGatt.getDevice(); return device.getBondState() == BluetoothDevice.BOND_BONDED; } @@ -632,10 +673,12 @@ import no.nordicsemi.android.dfu.internal.scanner.BootloaderScannerFactory; /** * Requests given MTU. This method is only supported on Android Lollipop or newer versions. * Only DFU from SDK 14.1 or newer supports MTU > 23. - * @param mtu new MTU to be requested + * + * @param mtu new MTU to be requested. */ @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) - protected void requestMtu(final int mtu) throws DeviceDisconnectedException, UploadAbortedException { + void requestMtu(@IntRange(from = 0, to = 517) final int mtu) + throws DeviceDisconnectedException, UploadAbortedException { if (mAborted) throw new UploadAbortedException(); mRequestCompleted = false; @@ -659,15 +702,18 @@ import no.nordicsemi.android.dfu.internal.scanner.BootloaderScannerFactory; } /** - * Waits until the notification will arrive. Returns the data returned by the notification. This method will block the thread until response is not ready or - * the device gets disconnected. If connection state will change, or an error will occur, an exception will be thrown. + * Waits until the notification will arrive. Returns the data returned by the notification. + * This method will block the thread until response is not ready or the device gets disconnected. + * If connection state will change, or an error will occur, an exception will be thrown. * * @return the value returned by the Control Point notification - * @throws DeviceDisconnectedException - * @throws DfuException - * @throws UploadAbortedException + * @throws DeviceDisconnectedException Thrown when the device will disconnect in the middle of + * the transmission. + * @throws DfuException Thrown if DFU error occur. + * @throws UploadAbortedException Thrown if DFU operation was aborted by user. */ - protected byte[] readNotificationResponse() throws DeviceDisconnectedException, DfuException, UploadAbortedException { + byte[] readNotificationResponse() + throws DeviceDisconnectedException, DfuException, UploadAbortedException { // do not clear the mReceiveData here. The response might already be obtained. Clear it in write request instead. try { synchronized (mLock) { @@ -689,10 +735,11 @@ import no.nordicsemi.android.dfu.internal.scanner.BootloaderScannerFactory; /** * Restarts the service based on the given intent. If parameter set this method will also scan for * an advertising bootloader that has address equal or incremented by 1 to the current one. - * @param intent the intent to be started as a service + * + * @param intent the intent to be started as a service * @param scanForBootloader true to scan for advertising bootloader, false to keep the same address */ - protected void restartService(final Intent intent, final boolean scanForBootloader) { + void restartService(@NonNull final Intent intent, final boolean scanForBootloader) { String newAddress = null; if (scanForBootloader) { mService.sendLogBroadcast(DfuBaseService.LOG_LEVEL_VERBOSE, "Scanning for the DFU Bootloader..."); @@ -714,7 +761,7 @@ import no.nordicsemi.android.dfu.internal.scanner.BootloaderScannerFactory; mService.startService(intent); } - protected String parse(final byte[] data) { + protected String parse(@Nullable final byte[] data) { if (data == null) return ""; diff --git a/dfu/src/main/java/no/nordicsemi/android/dfu/ButtonlessDfuImpl.java b/dfu/src/main/java/no/nordicsemi/android/dfu/ButtonlessDfuImpl.java index 65665d2..1c33304 100644 --- a/dfu/src/main/java/no/nordicsemi/android/dfu/ButtonlessDfuImpl.java +++ b/dfu/src/main/java/no/nordicsemi/android/dfu/ButtonlessDfuImpl.java @@ -28,6 +28,7 @@ import android.content.Intent; import java.util.Locale; +import androidx.annotation.NonNull; import no.nordicsemi.android.dfu.internal.exception.DeviceDisconnectedException; import no.nordicsemi.android.dfu.internal.exception.DfuException; import no.nordicsemi.android.dfu.internal.exception.RemoteDfuException; @@ -36,7 +37,8 @@ import no.nordicsemi.android.dfu.internal.exception.UploadAbortedException; import no.nordicsemi.android.error.SecureDfuError; /** - * A base class for buttonless service implementations made for Secure and in the future for Non-Secure DFU. + * A base class for buttonless service implementations made for Secure and in the future for + * Non-Secure DFU. */ /* package */ abstract class ButtonlessDfuImpl extends BaseButtonlessDfuImpl { @@ -44,34 +46,39 @@ import no.nordicsemi.android.error.SecureDfuError; private static final int OP_CODE_ENTER_BOOTLOADER_KEY = 0x01; private static final int OP_CODE_RESPONSE_CODE_KEY = 0x20; - private static final byte[] OP_CODE_ENTER_BOOTLOADER = new byte[] {OP_CODE_ENTER_BOOTLOADER_KEY}; + private static final byte[] OP_CODE_ENTER_BOOTLOADER = new byte[]{OP_CODE_ENTER_BOOTLOADER_KEY}; - ButtonlessDfuImpl(final Intent intent, final DfuBaseService service) { + ButtonlessDfuImpl(@NonNull final Intent intent, @NonNull final DfuBaseService service) { super(intent, service); } /** - * This method should return the type of the response received from the device after sending Enable Dfu command. - * Should be one of {@link #NOTIFICATIONS} or {@link #INDICATIONS}. - * @return response type + * This method should return the type of the response received from the device after sending + * Enable Dfu command. Should be one of {@link #NOTIFICATIONS} or {@link #INDICATIONS}. + * + * @return Response type. */ protected abstract int getResponseType(); /** * Returns the buttonless characteristic. - * @return the characteristic used to trigger buttonless jump to bootloader mode. + * + * @return The characteristic used to trigger buttonless jump to bootloader mode. */ protected abstract BluetoothGattCharacteristic getButtonlessDfuCharacteristic(); /** - * This method should return {@code true} if the bootloader is expected to start advertising with address - * incremented by 1. - * @return true if the bootloader may advertise with address +1, false if it will keep the same device address. + * This method should return {@code true} if the bootloader is expected to start advertising + * with address incremented by 1. + * + * @return True if the bootloader may advertise with address +1, false if it will keep + * the same device address. */ protected abstract boolean shouldScanForBootloader(); @Override - public void performDfu(final Intent intent) throws DfuException, DeviceDisconnectedException, UploadAbortedException { + public void performDfu(@NonNull final Intent intent) + throws DfuException, DeviceDisconnectedException, UploadAbortedException { mProgressInfo.setProgress(DfuBaseService.PROGRESS_STARTING); // Add one second delay to avoid the traffic jam before the DFU mode is enabled @@ -109,9 +116,10 @@ import no.nordicsemi.android.error.SecureDfuError; byte[] response; try { - // There may be a race condition here. The peripheral should send a notification and disconnect gracefully - // immediately after that, but the onConnectionStateChange event may be handled before this method ends. - // Also, sometimes the notification is not received at all. + // There may be a race condition here. The peripheral should send a notification + // and disconnect gracefully immediately after that, but the onConnectionStateChange + // event may be handled before this method ends. Also, sometimes the notification + // is not received at all. response = readNotificationResponse(); } catch (final DeviceDisconnectedException e) { // The device disconnect event was handled before the method finished, @@ -132,7 +140,8 @@ import no.nordicsemi.android.error.SecureDfuError; */ final int status = getStatusCode(response, OP_CODE_ENTER_BOOTLOADER_KEY); logi("Response received (Op Code = " + response[1] + ", Status = " + status + ")"); - mService.sendLogBroadcast(DfuBaseService.LOG_LEVEL_APPLICATION, "Response received (Op Code = " + response[1] + ", Status = " + status + ")"); + mService.sendLogBroadcast(DfuBaseService.LOG_LEVEL_APPLICATION, + "Response received (Op Code = " + response[1] + ", Status = " + status + ")"); if (status != DFU_STATUS_SUCCESS) throw new RemoteDfuException("Device returned error after sending Enter Bootloader", status); // The device will reset so we don't have to send Disconnect signal. @@ -152,7 +161,8 @@ import no.nordicsemi.android.error.SecureDfuError; } catch (final RemoteDfuException e) { final int error = DfuBaseService.ERROR_REMOTE_TYPE_SECURE_BUTTONLESS | e.getErrorNumber(); loge(e.getMessage()); - mService.sendLogBroadcast(DfuBaseService.LOG_LEVEL_ERROR, String.format(Locale.US, "Remote DFU error: %s", SecureDfuError.parseButtonlessError(error))); + mService.sendLogBroadcast(DfuBaseService.LOG_LEVEL_ERROR, String.format(Locale.US, + "Remote DFU error: %s", SecureDfuError.parseButtonlessError(error))); mService.terminateConnection(gatt, error | DfuBaseService.ERROR_REMOTE_MASK); } } @@ -161,13 +171,15 @@ import no.nordicsemi.android.error.SecureDfuError; * Checks whether the response received is valid and returns the status code. * * @param response the response received from the DFU device. - * @param request the expected Op Code - * @return the status code - * @throws UnknownResponseException if response was not valid + * @param request the expected Op Code. + * @return The status code. + * @throws UnknownResponseException if response was not valid. */ + @SuppressWarnings("SameParameterValue") private int getStatusCode(final byte[] response, final int request) throws UnknownResponseException { if (response == null || response.length < 3 || response[0] != OP_CODE_RESPONSE_CODE_KEY || response[1] != request || - (response[2] != DFU_STATUS_SUCCESS && response[2] != SecureDfuError.BUTTONLESS_ERROR_OP_CODE_NOT_SUPPORTED && response[2] != SecureDfuError.BUTTONLESS_ERROR_OPERATION_FAILED)) + (response[2] != DFU_STATUS_SUCCESS && response[2] != SecureDfuError.BUTTONLESS_ERROR_OP_CODE_NOT_SUPPORTED + && response[2] != SecureDfuError.BUTTONLESS_ERROR_OPERATION_FAILED)) throw new UnknownResponseException("Invalid response received", response, OP_CODE_RESPONSE_CODE_KEY, request); return response[2]; } diff --git a/dfu/src/main/java/no/nordicsemi/android/dfu/ButtonlessDfuWithBondSharingImpl.java b/dfu/src/main/java/no/nordicsemi/android/dfu/ButtonlessDfuWithBondSharingImpl.java index f14e359..3f63d43 100644 --- a/dfu/src/main/java/no/nordicsemi/android/dfu/ButtonlessDfuWithBondSharingImpl.java +++ b/dfu/src/main/java/no/nordicsemi/android/dfu/ButtonlessDfuWithBondSharingImpl.java @@ -29,34 +29,40 @@ import android.content.Intent; import java.util.UUID; +import androidx.annotation.NonNull; import no.nordicsemi.android.dfu.internal.exception.DeviceDisconnectedException; import no.nordicsemi.android.dfu.internal.exception.DfuException; import no.nordicsemi.android.dfu.internal.exception.UploadAbortedException; /** - * This implementation handles the secure buttonless DFU service that will be implemented in SDK 14 or later. - * - * This service requires the device to be paired, so that only a trusted phone can switch it to bootloader mode. - * The bond information will be shared to the bootloader and it will use the same device address when in DFU mode and - * the connection will be encrypted. + * This implementation handles the secure buttonless DFU service that will be implemented in + * SDK 14 or later. + *

    + * This service requires the device to be paired, so that only a trusted phone can switch it to + * bootloader mode. The bond information will be shared to the bootloader and it will use the + * same device address when in DFU mode and the connection will be encrypted. */ /* package */ class ButtonlessDfuWithBondSharingImpl extends ButtonlessDfuImpl { - /** The UUID of the Secure DFU service from SDK 12. */ - protected static final UUID DEFAULT_BUTTONLESS_DFU_SERVICE_UUID = SecureDfuImpl.DEFAULT_DFU_SERVICE_UUID; - /** The UUID of the Secure Buttonless DFU characteristic with bond sharing from SDK 14 or later (not released yet). */ - protected static final UUID DEFAULT_BUTTONLESS_DFU_UUID = new UUID(0x8EC90004F3154F60L, 0x9FB8838830DAEA50L); + /** + * The UUID of the Secure DFU service from SDK 12. + */ + static final UUID DEFAULT_BUTTONLESS_DFU_SERVICE_UUID = SecureDfuImpl.DEFAULT_DFU_SERVICE_UUID; + /** + * The UUID of the Secure Buttonless DFU characteristic with bond sharing from SDK 14 or newer. + */ + static final UUID DEFAULT_BUTTONLESS_DFU_UUID = new UUID(0x8EC90004F3154F60L, 0x9FB8838830DAEA50L); - protected static UUID BUTTONLESS_DFU_SERVICE_UUID = DEFAULT_BUTTONLESS_DFU_SERVICE_UUID; - protected static UUID BUTTONLESS_DFU_UUID = DEFAULT_BUTTONLESS_DFU_UUID; + static UUID BUTTONLESS_DFU_SERVICE_UUID = DEFAULT_BUTTONLESS_DFU_SERVICE_UUID; + static UUID BUTTONLESS_DFU_UUID = DEFAULT_BUTTONLESS_DFU_UUID; private BluetoothGattCharacteristic mButtonlessDfuCharacteristic; - ButtonlessDfuWithBondSharingImpl(final Intent intent, final DfuBaseService service) { + ButtonlessDfuWithBondSharingImpl(@NonNull final Intent intent, @NonNull final DfuBaseService service) { super(intent, service); } @Override - public boolean isClientCompatible(final Intent intent, final BluetoothGatt gatt) { + public boolean isClientCompatible(@NonNull final Intent intent, @NonNull final BluetoothGatt gatt) { final BluetoothGattService dfuService = gatt.getService(BUTTONLESS_DFU_SERVICE_UUID); if (dfuService == null) return false; @@ -83,7 +89,8 @@ import no.nordicsemi.android.dfu.internal.exception.UploadAbortedException; } @Override - public void performDfu(final Intent intent) throws DfuException, DeviceDisconnectedException, UploadAbortedException { + public void performDfu(@NonNull final Intent intent) + throws DfuException, DeviceDisconnectedException, UploadAbortedException { logi("Buttonless service with bond sharing found -> SDK 14 or newer"); if (!isBonded()) { logw("Device is not paired, cancelling DFU"); diff --git a/dfu/src/main/java/no/nordicsemi/android/dfu/ButtonlessDfuWithoutBondSharingImpl.java b/dfu/src/main/java/no/nordicsemi/android/dfu/ButtonlessDfuWithoutBondSharingImpl.java index efdb828..897dddd 100644 --- a/dfu/src/main/java/no/nordicsemi/android/dfu/ButtonlessDfuWithoutBondSharingImpl.java +++ b/dfu/src/main/java/no/nordicsemi/android/dfu/ButtonlessDfuWithoutBondSharingImpl.java @@ -29,6 +29,7 @@ import android.content.Intent; import java.util.UUID; +import androidx.annotation.NonNull; import no.nordicsemi.android.dfu.internal.exception.DeviceDisconnectedException; import no.nordicsemi.android.dfu.internal.exception.DfuException; import no.nordicsemi.android.dfu.internal.exception.UploadAbortedException; @@ -37,35 +38,38 @@ import no.nordicsemi.android.dfu.internal.exception.UploadAbortedException; * This implementation handles 2 services: * - a non-secure buttonless DFU service introduced in SDK 13 * - a secure buttonless DFU service that will be implemented in some next SDK (14 or later) - * - * An application that supports one of those services should have the Secure DFU Service with one of those characteristic inside. - * - * The non-secure one does not share the bond information to the bootloader, and the bootloader starts advertising with address +1 after the jump. - * It may be used by a bonded devices (it's even recommended, as it prevents from DoS attack), but the connection with the bootloader will not - * be encrypted (in Secure DFU it is not an issue as the firmware itself is signed). When implemented on a non-bonded device it is - * important to understand, that anyone could connect to the device and switch it to DFU mode preventing the device from normal usage. - * - * The secure one requires the device to be paired so that only the trusted phone can switch it to bootloader mode. - * The bond information will be shared to the bootloader so it will use the same device address when in DFU mode and - * the connection will be encrypted. + *

    + * An application that supports one of those services should have the Secure DFU Service with one + * of those characteristic inside. + *

    + * The non-secure one does not share the bond information to the bootloader, and the bootloader + * starts advertising with address +1 after the jump. It may be used by a bonded devices + * (it's even recommended, as it prevents from DoS attack), but the connection with the bootloader + * will not be encrypted (in Secure DFU it is not an issue as the firmware itself is signed). + * When implemented on a non-bonded device it is important to understand, that anyone could + * connect to the device and switch it to DFU mode preventing the device from normal usage. + *

    + * The secure one requires the device to be paired so that only the trusted phone can switch it + * to bootloader mode. The bond information will be shared to the bootloader so it will use the + * same device address when in DFU mode and the connection will be encrypted. */ /* package */ class ButtonlessDfuWithoutBondSharingImpl extends ButtonlessDfuImpl { /** The UUID of the Secure DFU service from SDK 12. */ - protected static final UUID DEFAULT_BUTTONLESS_DFU_SERVICE_UUID = SecureDfuImpl.DEFAULT_DFU_SERVICE_UUID; + static final UUID DEFAULT_BUTTONLESS_DFU_SERVICE_UUID = SecureDfuImpl.DEFAULT_DFU_SERVICE_UUID; /** The UUID of the Secure Buttonless DFU characteristic without bond sharing from SDK 13. */ - protected static final UUID DEFAULT_BUTTONLESS_DFU_UUID = new UUID(0x8EC90003F3154F60L, 0x9FB8838830DAEA50L); + static final UUID DEFAULT_BUTTONLESS_DFU_UUID = new UUID(0x8EC90003F3154F60L, 0x9FB8838830DAEA50L); - protected static UUID BUTTONLESS_DFU_SERVICE_UUID = DEFAULT_BUTTONLESS_DFU_SERVICE_UUID; - protected static UUID BUTTONLESS_DFU_UUID = DEFAULT_BUTTONLESS_DFU_UUID; + static UUID BUTTONLESS_DFU_SERVICE_UUID = DEFAULT_BUTTONLESS_DFU_SERVICE_UUID; + static UUID BUTTONLESS_DFU_UUID = DEFAULT_BUTTONLESS_DFU_UUID; private BluetoothGattCharacteristic mButtonlessDfuCharacteristic; - ButtonlessDfuWithoutBondSharingImpl(final Intent intent, final DfuBaseService service) { + ButtonlessDfuWithoutBondSharingImpl(@NonNull final Intent intent, @NonNull final DfuBaseService service) { super(intent, service); } @Override - public boolean isClientCompatible(final Intent intent, final BluetoothGatt gatt) { + public boolean isClientCompatible(@NonNull final Intent intent, @NonNull final BluetoothGatt gatt) { final BluetoothGattService dfuService = gatt.getService(BUTTONLESS_DFU_SERVICE_UUID); if (dfuService == null) return false; @@ -92,7 +96,8 @@ import no.nordicsemi.android.dfu.internal.exception.UploadAbortedException; } @Override - public void performDfu(final Intent intent) throws DfuException, DeviceDisconnectedException, UploadAbortedException { + public void performDfu(@NonNull final Intent intent) + throws DfuException, DeviceDisconnectedException, UploadAbortedException { logi("Buttonless service without bond sharing found -> SDK 13 or newer"); if (isBonded()) { logw("Device is paired! Use Buttonless DFU with Bond Sharing instead (SDK 14 or newer)"); diff --git a/dfu/src/main/java/no/nordicsemi/android/dfu/DfuBaseService.java b/dfu/src/main/java/no/nordicsemi/android/dfu/DfuBaseService.java index f9941cc..d3982fc 100644 --- a/dfu/src/main/java/no/nordicsemi/android/dfu/DfuBaseService.java +++ b/dfu/src/main/java/no/nordicsemi/android/dfu/DfuBaseService.java @@ -46,6 +46,9 @@ import android.os.Build; import android.os.SystemClock; import android.preference.PreferenceManager; import android.provider.MediaStore; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import androidx.core.app.NotificationCompat; import androidx.localbroadcastmanager.content.LocalBroadcastManager; import android.util.Log; @@ -1388,15 +1391,17 @@ public abstract class DfuBaseService extends IntentService implements DfuProgres } /** - * Opens the binary input stream that returns the firmware image content. A Path to the file is given. + * Opens the binary input stream that returns the firmware image content. + * A Path to the file is given. * - * @param filePath the path to the HEX, BIN or ZIP file - * @param mimeType the file type - * @param mbrSize the size of MBR, by default 0x1000 - * @param types the content files types in ZIP - * @return the input stream with binary image content + * @param filePath the path to the HEX, BIN or ZIP file. + * @param mimeType the file type. + * @param mbrSize the size of MBR, by default 0x1000. + * @param types the content files types in ZIP. + * @return The input stream with binary image content. */ - private InputStream openInputStream(final String filePath, final String mimeType, final int mbrSize, final int types) throws IOException { + private InputStream openInputStream(@NonNull final String filePath, final String mimeType, final int mbrSize, final int types) + throws IOException { final InputStream is = new FileInputStream(filePath); if (MIME_TYPE_ZIP.equals(mimeType)) return new ArchiveInputStream(is, mbrSize, types); @@ -1408,13 +1413,14 @@ public abstract class DfuBaseService extends IntentService implements DfuProgres /** * Opens the binary input stream. A Uri to the stream is given. * - * @param stream the Uri to the stream - * @param mimeType the file type - * @param mbrSize the size of MBR, by default 0x1000 - * @param types the content files types in ZIP - * @return the input stream with binary image content + * @param stream the Uri to the stream. + * @param mimeType the file type. + * @param mbrSize the size of MBR, by default 0x1000. + * @param types the content files types in ZIP. + * @return The input stream with binary image content. */ - private InputStream openInputStream(final Uri stream, final String mimeType, final int mbrSize, final int types) throws IOException { + private InputStream openInputStream(@NonNull final Uri stream, final String mimeType, final int mbrSize, final int types) + throws IOException { final InputStream is = getContentResolver().openInputStream(stream); if (MIME_TYPE_ZIP.equals(mimeType)) return new ArchiveInputStream(is, mbrSize, types); @@ -1435,15 +1441,17 @@ public abstract class DfuBaseService extends IntentService implements DfuProgres } /** - * Opens the binary input stream that returns the firmware image content. A resource id in the res/raw is given. + * Opens the binary input stream that returns the firmware image content. + * A resource id in the res/raw is given. * - * @param resId the if of the resource file - * @param mimeType the file type - * @param mbrSize the size of MBR, by default 0x1000 - * @param types the content files types in ZIP - * @return the input stream with binary image content + * @param resId the if of the resource file. + * @param mimeType the file type. + * @param mbrSize the size of MBR, by default 0x1000. + * @param types the content files types in ZIP. + * @return The input stream with binary image content. */ - private InputStream openInputStream(final int resId, final String mimeType, final int mbrSize, final int types) throws IOException { + private InputStream openInputStream(final int resId, final String mimeType, final int mbrSize, final int types) + throws IOException { final InputStream is = getResources().openRawResource(resId); if (MIME_TYPE_ZIP.equals(mimeType)) return new ArchiveInputStream(is, mbrSize, types); @@ -1456,13 +1464,15 @@ public abstract class DfuBaseService extends IntentService implements DfuProgres } /** - * Connects to the BLE device with given address. This method is SYNCHRONOUS, it wait until the connection status change from {@link #STATE_CONNECTING} to {@link #STATE_CONNECTED_AND_READY} or an - * error occurs. This method returns null if Bluetooth adapter is disabled. + * Connects to the BLE device with given address. This method is SYNCHRONOUS, it wait until + * the connection status change from {@link #STATE_CONNECTING} to + * {@link #STATE_CONNECTED_AND_READY} or an error occurs. + * This method returns null if Bluetooth adapter is disabled. * - * @param address the device address - * @return the GATT device or null if Bluetooth adapter is disabled. + * @param address the device address. + * @return The GATT device or null if Bluetooth adapter is disabled. */ - protected BluetoothGatt connect(final String address) { + protected BluetoothGatt connect(@NonNull final String address) { if (!mBluetoothAdapter.isEnabled()) return null; @@ -1487,12 +1497,13 @@ public abstract class DfuBaseService extends IntentService implements DfuProgres } /** - * Disconnects from the device and cleans local variables in case of error. This method is SYNCHRONOUS and wait until the disconnecting process will be completed. + * Disconnects from the device and cleans local variables in case of error. + * This method is SYNCHRONOUS and wait until the disconnecting process will be completed. * - * @param gatt the GATT device to be disconnected - * @param error error number + * @param gatt the GATT device to be disconnected. + * @param error error number. */ - protected void terminateConnection(final BluetoothGatt gatt, final int error) { + protected void terminateConnection(@NonNull final BluetoothGatt gatt, final int error) { if (mConnectionState != STATE_DISCONNECTED) { // Disconnect from the device disconnect(gatt); @@ -1507,12 +1518,13 @@ public abstract class DfuBaseService extends IntentService implements DfuProgres } /** - * Disconnects from the device. This is SYNCHRONOUS method and waits until the callback returns new state. Terminates immediately if device is already disconnected. Do not call this method + * Disconnects from the device. This is SYNCHRONOUS method and waits until the callback returns + * new state. Terminates immediately if device is already disconnected. Do not call this method * directly, use {@link #terminateConnection(android.bluetooth.BluetoothGatt, int)} instead. * - * @param gatt the GATT device that has to be disconnected + * @param gatt the GATT device that has to be disconnected. */ - protected void disconnect(final BluetoothGatt gatt) { + protected void disconnect(@NonNull final BluetoothGatt gatt) { if (mConnectionState == STATE_DISCONNECTED) return; @@ -1530,7 +1542,8 @@ public abstract class DfuBaseService extends IntentService implements DfuProgres } /** - * Wait until the connection state will change to {@link #STATE_DISCONNECTED} or until an error occurs. + * Wait until the connection state will change to {@link #STATE_DISCONNECTED} or until + * an error occurs. */ protected void waitUntilDisconnected() { try { @@ -1545,7 +1558,8 @@ public abstract class DfuBaseService extends IntentService implements DfuProgres /** * Wait for given number of milliseconds. - * @param millis waiting period + * + * @param millis waiting period. */ protected void waitFor(final int millis) { synchronized (mLock) { @@ -1561,7 +1575,7 @@ public abstract class DfuBaseService extends IntentService implements DfuProgres /** * Closes the GATT device and cleans up. * - * @param gatt the GATT device to be closed + * @param gatt the GATT device to be closed. */ protected void close(final BluetoothGatt gatt) { logi("Cleaning up..."); @@ -1571,10 +1585,11 @@ public abstract class DfuBaseService extends IntentService implements DfuProgres } /** - * Clears the device cache. After uploading new firmware the DFU target will have other services than before. + * Clears the device cache. After uploading new firmware the DFU target will have other + * services than before. * - * @param gatt the GATT device to be refreshed - * @param force true to force the refresh + * @param gatt the GATT device to be refreshed. + * @param force true to force the refresh. */ protected void refreshDeviceCache(final BluetoothGatt gatt, final boolean force) { /* @@ -1601,7 +1616,8 @@ public abstract class DfuBaseService extends IntentService implements DfuProgres } /** - * Creates or updates the notification in the Notification Manager. Sends broadcast with given progress state to the activity. + * Creates or updates the notification in the Notification Manager. Sends broadcast with + * given progress state to the activity. */ @Override public void updateProgressNotification() { @@ -1690,9 +1706,10 @@ public abstract class DfuBaseService extends IntentService implements DfuProgres /** * This method allows you to update the notification showing the upload progress. - * @param builder notification builder + * + * @param builder notification builder. */ - protected void updateProgressNotification(final NotificationCompat.Builder builder, final int progress) { + protected void updateProgressNotification(@NonNull final NotificationCompat.Builder builder, final int progress) { // Add Abort action to the notification if (progress != PROGRESS_ABORTED && progress != PROGRESS_COMPLETED) { final Intent abortIntent = new Intent(BROADCAST_ACTION); @@ -1703,9 +1720,10 @@ public abstract class DfuBaseService extends IntentService implements DfuProgres } /** - * Creates or updates the notification in the Notification Manager. Sends broadcast with given error numbre to the activity. + * Creates or updates the notification in the Notification Manager. Sends broadcast with given + * error number to the activity. * - * @param error the error number + * @param error the error number. */ private void report(final int error) { sendErrorBroadcast(error); @@ -1748,7 +1766,7 @@ public abstract class DfuBaseService extends IntentService implements DfuProgres * @param builder error notification builder */ @SuppressWarnings("unused") - protected void updateErrorNotification(final NotificationCompat.Builder builder) { + protected void updateErrorNotification(@NonNull final NotificationCompat.Builder builder) { // Empty default implementation } @@ -1781,11 +1799,13 @@ public abstract class DfuBaseService extends IntentService implements DfuProgres } /** - * This method allows you to update the notification that will be shown when the service goes to the foreground state. + * This method allows you to update the notification that will be shown when the service goes to + * the foreground state. + * * @param builder foreground notification builder */ @SuppressWarnings("unused") - protected void updateForegroundNotification(final NotificationCompat.Builder builder) { + protected void updateForegroundNotification(@NonNull final NotificationCompat.Builder builder) { // Empty default implementation } @@ -1801,15 +1821,13 @@ public abstract class DfuBaseService extends IntentService implements DfuProgres * or error number if {@link #ERROR_MASK} bit set. * *

    - * The {@link #EXTRA_PROGRESS} is not set when a notification indicating a foreground service - * was clicked and notifications were disabled using {@link DfuServiceInitiator#setDisableNotification(boolean)}. - *

    + * The {@link #EXTRA_PROGRESS} is not set when a notification indicating a foreground service + * was clicked and notifications were disabled using {@link DfuServiceInitiator#setDisableNotification(boolean)}. *

    - * If your application disabled DFU notifications by calling + * If your application disabled DFU notifications by calling * {@link DfuServiceInitiator#setDisableNotification(boolean)} with parameter true this method * will still be called if the service was started as foreground service. To disable foreground service * call {@link DfuServiceInitiator#setForeground(boolean)} with parameter false. - *

    * _______________________________
    * * - connection state constants: *
      @@ -1822,8 +1840,9 @@ public abstract class DfuBaseService extends IntentService implements DfuProgres *
    • {@link #PROGRESS_VALIDATING}
    • *
    * - * @return the target activity class + * @return The target activity class. */ + @Nullable protected abstract Class getNotificationTarget(); /** @@ -1835,11 +1854,12 @@ public abstract class DfuBaseService extends IntentService implements DfuProgres * return BuildConfig.DEBUG; * } * - * @return true to enable LogCat output, false (default) if not + * @return True to enable LogCat output, false (default) if not. */ protected boolean isDebug() { // Override this method and return true if you need more logs in LogCat - // Note: BuildConfig.DEBUG always returns false in library projects, so please use your app package BuildConfig + // Note: BuildConfig.DEBUG always returns false in library projects, so please use + // your app package BuildConfig return false; } @@ -1883,9 +1903,9 @@ public abstract class DfuBaseService extends IntentService implements DfuProgres } /** - * Initializes bluetooth adapter + * Initializes bluetooth adapter. * - * @return true if initialization was successful + * @return True if initialization was successful. */ @SuppressWarnings("UnusedReturnValue") private boolean initialize() { diff --git a/dfu/src/main/java/no/nordicsemi/android/dfu/DfuCallback.java b/dfu/src/main/java/no/nordicsemi/android/dfu/DfuCallback.java index 86c6fd5..c5dbb5d 100644 --- a/dfu/src/main/java/no/nordicsemi/android/dfu/DfuCallback.java +++ b/dfu/src/main/java/no/nordicsemi/android/dfu/DfuCallback.java @@ -32,9 +32,15 @@ import android.bluetooth.BluetoothGattCallback; } } - /** Returns the final BluetoothGattCallback instance, depending on the implementation. */ + /** + * Returns the final BluetoothGattCallback instance, depending on the implementation. + */ DfuGattCallback getGattCallback(); - /** Callback invoked when bond state changes to {@link android.bluetooth.BluetoothDevice#BOND_BONDED BOND_BONDED} or {@link android.bluetooth.BluetoothDevice#BOND_NONE BOND_NONE}. */ + /** + * Callback invoked when bond state changes to + * {@link android.bluetooth.BluetoothDevice#BOND_BONDED BOND_BONDED} or + * {@link android.bluetooth.BluetoothDevice#BOND_NONE BOND_NONE}. + */ void onBondStateChanged(final int state); } diff --git a/dfu/src/main/java/no/nordicsemi/android/dfu/DfuController.java b/dfu/src/main/java/no/nordicsemi/android/dfu/DfuController.java index 0348053..8c34cf1 100644 --- a/dfu/src/main/java/no/nordicsemi/android/dfu/DfuController.java +++ b/dfu/src/main/java/no/nordicsemi/android/dfu/DfuController.java @@ -22,7 +22,7 @@ package no.nordicsemi.android.dfu; -/* package */ public interface DfuController { +public interface DfuController { /** * Pauses the DFU operation. Call {@link #resume()} to resume, or {@link #abort()} to cancel. diff --git a/dfu/src/main/java/no/nordicsemi/android/dfu/DfuLogListener.java b/dfu/src/main/java/no/nordicsemi/android/dfu/DfuLogListener.java index 81b0cdc..40e8bcb 100644 --- a/dfu/src/main/java/no/nordicsemi/android/dfu/DfuLogListener.java +++ b/dfu/src/main/java/no/nordicsemi/android/dfu/DfuLogListener.java @@ -23,12 +23,15 @@ package no.nordicsemi.android.dfu; /** - * Listener for log events. This listener should be used instead of creating the BroadcastReceiver on your own. + * Listener for log events. This listener should be used instead of creating the + * BroadcastReceiver on your own. + * * @see DfuServiceListenerHelper */ public interface DfuLogListener { /** * Method called when a log event was sent from the DFU service. + * * @param deviceAddress the target device address * @param level the log level, one of: *
      diff --git a/dfu/src/main/java/no/nordicsemi/android/dfu/DfuProgressInfo.java b/dfu/src/main/java/no/nordicsemi/android/dfu/DfuProgressInfo.java index 969d7d0..fb7d61d 100644 --- a/dfu/src/main/java/no/nordicsemi/android/dfu/DfuProgressInfo.java +++ b/dfu/src/main/java/no/nordicsemi/android/dfu/DfuProgressInfo.java @@ -54,12 +54,13 @@ import androidx.annotation.NonNull; return this; } + @SuppressWarnings({"UnusedReturnValue", "SameParameterValue"}) DfuProgressInfo setTotalPart(final int totalParts) { this.totalParts = totalParts; return this; } - public void setProgress(final int progress) { + void setProgress(final int progress) { this.progress = progress; mListener.updateProgressNotification(); } @@ -108,10 +109,12 @@ import androidx.annotation.NonNull; return bytesSent; } + @SuppressWarnings("unused") int getBytesReceived() { return bytesReceived; } + @SuppressWarnings("unused") int getImageSizeInBytes() { return imageSizeInBytes; } diff --git a/dfu/src/main/java/no/nordicsemi/android/dfu/DfuProgressListener.java b/dfu/src/main/java/no/nordicsemi/android/dfu/DfuProgressListener.java index e40c59a..72e48f9 100644 --- a/dfu/src/main/java/no/nordicsemi/android/dfu/DfuProgressListener.java +++ b/dfu/src/main/java/no/nordicsemi/android/dfu/DfuProgressListener.java @@ -22,92 +22,120 @@ package no.nordicsemi.android.dfu; +import androidx.annotation.NonNull; + /** - * Listener for status, progress and error events. This listener should be used instead of creating the BroadcastReceiver on your own. + * Listener for status, progress and error events. This listener should be used instead of + * creating the BroadcastReceiver on your own. + * * @see DfuServiceListenerHelper */ public interface DfuProgressListener { /** * Method called when the DFU service started connecting with the DFU target. - * @param deviceAddress the target device address + * + * @param deviceAddress the target device address. */ - void onDeviceConnecting(final String deviceAddress); + void onDeviceConnecting(@NonNull final String deviceAddress); /** - * Method called when the service has successfully connected, discovered services and found DFU service on the DFU target. - * @param deviceAddress the target device address + * Method called when the service has successfully connected, discovered services and found + * DFU service on the DFU target. + * + * @param deviceAddress the target device address. */ - void onDeviceConnected(final String deviceAddress); + void onDeviceConnected(@NonNull final String deviceAddress); /** - * Method called when the DFU process is starting. This includes reading the DFU Version characteristic, sending DFU_START command as well as the Init packet, if set. - * @param deviceAddress the target device address + * Method called when the DFU process is starting. This includes reading the DFU Version + * characteristic, sending DFU_START command as well as the Init packet, if set. + * + * @param deviceAddress the target device address. */ - void onDfuProcessStarting(final String deviceAddress); + void onDfuProcessStarting(@NonNull final String deviceAddress); /** * Method called when the DFU process was started and bytes about to be sent. + * * @param deviceAddress the target device address */ - void onDfuProcessStarted(final String deviceAddress); + void onDfuProcessStarted(@NonNull final String deviceAddress); /** - * Method called when the service discovered that the DFU target is in the application mode and must be switched to DFU mode. - * The switch command will be sent and the DFU process should start again. There will be no {@link #onDeviceDisconnected(String)} event following this call. - * @param deviceAddress the target device address + * Method called when the service discovered that the DFU target is in the application mode + * and must be switched to DFU mode. The switch command will be sent and the DFU process + * should start again. There will be no {@link #onDeviceDisconnected(String)} event following + * this call. + * + * @param deviceAddress the target device address. */ - void onEnablingDfuMode(final String deviceAddress); + void onEnablingDfuMode(@NonNull final String deviceAddress); /** - * Method called during uploading the firmware. It will not be called twice with the same value of percent, however, in case of small firmware files, some values may be omitted. - * @param deviceAddress the target device address - * @param percent the current status of upload (0-99) - * @param speed the current speed in bytes per millisecond - * @param avgSpeed the average speed in bytes per millisecond - * @param currentPart the number pf part being sent. In case the ZIP file contains a Soft Device and/or a Bootloader together with the application the SD+BL are sent as part 1, - * then the service starts again and send the application as part 2 - * @param partsTotal total number of parts + * Method called during uploading the firmware. It will not be called twice with the same + * value of percent, however, in case of small firmware files, some values may be omitted. + * + * @param deviceAddress the target device address. + * @param percent the current status of upload (0-99). + * @param speed the current speed in bytes per millisecond. + * @param avgSpeed the average speed in bytes per millisecond + * @param currentPart the number pf part being sent. In case the ZIP file contains a Soft Device + * and/or a Bootloader together with the application the SD+BL are sent as part 1, + * then the service starts again and send the application as part 2. + * @param partsTotal total number of parts. */ - void onProgressChanged(final String deviceAddress, final int percent, final float speed, final float avgSpeed, final int currentPart, final int partsTotal); + void onProgressChanged(@NonNull final String deviceAddress, final int percent, + final float speed, final float avgSpeed, + final int currentPart, final int partsTotal); /** * Method called when the new firmware is being validated on the target device. - * @param deviceAddress the target device address + * + * @param deviceAddress the target device address. */ - void onFirmwareValidating(final String deviceAddress); + void onFirmwareValidating(@NonNull final String deviceAddress); /** * Method called when the service started to disconnect from the target device. - * @param deviceAddress the target device address + * + * @param deviceAddress the target device address. */ void onDeviceDisconnecting(final String deviceAddress); /** * Method called when the service disconnected from the device. The device has been reset. - * @param deviceAddress the target device address + * + * @param deviceAddress the target device address. */ - void onDeviceDisconnected(final String deviceAddress); + void onDeviceDisconnected(@NonNull final String deviceAddress); /** * Method called when the DFU process succeeded. - * @param deviceAddress the target device address + * + * @param deviceAddress the target device address. */ - void onDfuCompleted(final String deviceAddress); + void onDfuCompleted(@NonNull final String deviceAddress); /** * Method called when the DFU process has been aborted. - * @param deviceAddress the target device address + * + * @param deviceAddress the target device address. */ - void onDfuAborted(final String deviceAddress); + void onDfuAborted(@NonNull final String deviceAddress); /** * Method called when an error occur. - * @param deviceAddress the target device address - * @param error error number - * @param errorType the error type, one of {@link DfuBaseService#ERROR_TYPE_COMMUNICATION_STATE}, {@link DfuBaseService#ERROR_TYPE_COMMUNICATION}, - * {@link DfuBaseService#ERROR_TYPE_DFU_REMOTE}, {@link DfuBaseService#ERROR_TYPE_OTHER}. - * @param message the error message + * + * @param deviceAddress the target device address. + * @param error error number. + * @param errorType the error type, one of + * {@link DfuBaseService#ERROR_TYPE_COMMUNICATION_STATE}, + * {@link DfuBaseService#ERROR_TYPE_COMMUNICATION}, + * {@link DfuBaseService#ERROR_TYPE_DFU_REMOTE}, + * {@link DfuBaseService#ERROR_TYPE_OTHER}. + * @param message the error message. */ - void onError(final String deviceAddress, final int error, final int errorType, final String message); + void onError(@NonNull final String deviceAddress, + final int error, final int errorType, final String message); } diff --git a/dfu/src/main/java/no/nordicsemi/android/dfu/DfuProgressListenerAdapter.java b/dfu/src/main/java/no/nordicsemi/android/dfu/DfuProgressListenerAdapter.java index 3490f12..2cb76da 100644 --- a/dfu/src/main/java/no/nordicsemi/android/dfu/DfuProgressListenerAdapter.java +++ b/dfu/src/main/java/no/nordicsemi/android/dfu/DfuProgressListenerAdapter.java @@ -22,39 +22,44 @@ package no.nordicsemi.android.dfu; +import androidx.annotation.NonNull; + public class DfuProgressListenerAdapter implements DfuProgressListener { + @Override - public void onDeviceConnecting(final String deviceAddress) { + public void onDeviceConnecting(@NonNull final String deviceAddress) { // empty default implementation } @Override - public void onDeviceConnected(final String deviceAddress) { + public void onDeviceConnected(@NonNull final String deviceAddress) { // empty default implementation } @Override - public void onDfuProcessStarting(final String deviceAddress) { + public void onDfuProcessStarting(@NonNull final String deviceAddress) { // empty default implementation } @Override - public void onDfuProcessStarted(final String deviceAddress) { + public void onDfuProcessStarted(@NonNull final String deviceAddress) { // empty default implementation } @Override - public void onEnablingDfuMode(final String deviceAddress) { + public void onEnablingDfuMode(@NonNull final String deviceAddress) { // empty default implementation } @Override - public void onProgressChanged(final String deviceAddress, final int percent, final float speed, final float avgSpeed, final int currentPart, final int partsTotal) { + public void onProgressChanged(@NonNull final String deviceAddress, final int percent, + final float speed, final float avgSpeed, + final int currentPart, final int partsTotal) { // empty default implementation } @Override - public void onFirmwareValidating(final String deviceAddress) { + public void onFirmwareValidating(@NonNull final String deviceAddress) { // empty default implementation } @@ -64,22 +69,23 @@ public class DfuProgressListenerAdapter implements DfuProgressListener { } @Override - public void onDeviceDisconnected(final String deviceAddress) { + public void onDeviceDisconnected(@NonNull final String deviceAddress) { // empty default implementation } @Override - public void onDfuCompleted(final String deviceAddress) { + public void onDfuCompleted(@NonNull final String deviceAddress) { // empty default implementation } @Override - public void onDfuAborted(final String deviceAddress) { + public void onDfuAborted(@NonNull final String deviceAddress) { // empty default implementation } @Override - public void onError(final String deviceAddress, final int error, final int errorType, final String message) { + public void onError(@NonNull final String deviceAddress, + final int error, final int errorType, final String message) { // empty default implementation } } diff --git a/dfu/src/main/java/no/nordicsemi/android/dfu/DfuScope.java b/dfu/src/main/java/no/nordicsemi/android/dfu/DfuScope.java index 31323c7..66d185c 100644 --- a/dfu/src/main/java/no/nordicsemi/android/dfu/DfuScope.java +++ b/dfu/src/main/java/no/nordicsemi/android/dfu/DfuScope.java @@ -2,6 +2,7 @@ package no.nordicsemi.android.dfu; import androidx.annotation.IntDef; +@SuppressWarnings("WeakerAccess") @IntDef(value = { DfuServiceInitiator.SCOPE_SYSTEM_COMPONENTS, DfuServiceInitiator.SCOPE_APPLICATION diff --git a/dfu/src/main/java/no/nordicsemi/android/dfu/DfuService.java b/dfu/src/main/java/no/nordicsemi/android/dfu/DfuService.java index 5eaae7f..17735f4 100644 --- a/dfu/src/main/java/no/nordicsemi/android/dfu/DfuService.java +++ b/dfu/src/main/java/no/nordicsemi/android/dfu/DfuService.java @@ -27,24 +27,55 @@ import android.content.Intent; import java.io.InputStream; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import no.nordicsemi.android.dfu.internal.exception.DeviceDisconnectedException; import no.nordicsemi.android.dfu.internal.exception.DfuException; import no.nordicsemi.android.dfu.internal.exception.UploadAbortedException; /* package */ interface DfuService extends DfuCallback { - /** This method must return true if the device is compatible with this DFU implementation, false otherwise. */ - boolean isClientCompatible(final Intent intent, final BluetoothGatt gatt) throws DfuException, DeviceDisconnectedException, UploadAbortedException; + /** + * This method must return true if the device is compatible with this DFU implementation, + * false otherwise. + * + * @throws DeviceDisconnectedException Thrown when the device will disconnect in the middle of + * the transmission. + * @throws DfuException Thrown if DFU error occur. + * @throws UploadAbortedException Thrown if DFU operation was aborted by user. + */ + boolean isClientCompatible(@NonNull final Intent intent, @NonNull final BluetoothGatt gatt) + throws DfuException, DeviceDisconnectedException, UploadAbortedException; /** * Initializes the DFU implementation and does some initial setting up. - * @return true if initialization was successful and the DFU process may begin, false to finish teh DFU service + * + * @return True if initialization was successful and the DFU process may begin, + * false to finish teh DFU service. + * @throws DeviceDisconnectedException Thrown when the device will disconnect in the middle of + * the transmission. + * @throws DfuException Thrown if DFU error occur. + * @throws UploadAbortedException Thrown if DFU operation was aborted by user. */ - boolean initialize(final Intent intent, final BluetoothGatt gatt, final int fileType, final InputStream firmwareStream, final InputStream initPacketStream) throws DfuException, DeviceDisconnectedException, UploadAbortedException; + boolean initialize(@NonNull final Intent intent, @NonNull final BluetoothGatt gatt, + @FileType final int fileType, + @NonNull final InputStream firmwareStream, + @Nullable final InputStream initPacketStream) + throws DfuException, DeviceDisconnectedException, UploadAbortedException; - /** Performs the DFU process. */ - void performDfu(final Intent intent) throws DfuException, DeviceDisconnectedException, UploadAbortedException; + /** + * Performs the DFU process. + * + * @throws DeviceDisconnectedException Thrown when the device will disconnect in the middle of + * the transmission. + * @throws DfuException Thrown if DFU error occur. + * @throws UploadAbortedException Thrown if DFU operation was aborted by user. + */ + void performDfu(@NonNull final Intent intent) + throws DfuException, DeviceDisconnectedException, UploadAbortedException; - /** Releases the service. */ + /** + * Releases the service. + */ void release(); } diff --git a/dfu/src/main/java/no/nordicsemi/android/dfu/DfuServiceController.java b/dfu/src/main/java/no/nordicsemi/android/dfu/DfuServiceController.java index 184cafb..7ba51e1 100644 --- a/dfu/src/main/java/no/nordicsemi/android/dfu/DfuServiceController.java +++ b/dfu/src/main/java/no/nordicsemi/android/dfu/DfuServiceController.java @@ -24,13 +24,18 @@ package no.nordicsemi.android.dfu; import android.content.Context; import android.content.Intent; + +import androidx.annotation.NonNull; import androidx.localbroadcastmanager.content.LocalBroadcastManager; /** * A controller class allows you to pause, resume or abort the DFU operation in a easy way. - *

      Keep in mind that there may be only one DFU operation at a time, and other instances of a DfuServiceController (for example obtained with a previous DFU) - * will work for all DFU processes, but the {@link #isPaused()} and {@link #isAborted()} methods may report incorrect values.

      - *

      Added in DFU Library version 1.0.2.

      + *

      + * Keep in mind that there may be only one DFU operation at a time, and other instances of + * a DfuServiceController (for example obtained with a previous DFU) will work for all DFU processes, + * but the {@link #isPaused()} and {@link #isAborted()} methods may report incorrect values. + *

      + * Added in DFU Library version 1.0.2. */ @SuppressWarnings({"WeakerAccess", "unused"}) public class DfuServiceController implements DfuController { @@ -38,7 +43,7 @@ public class DfuServiceController implements DfuController { private boolean mPaused; private boolean mAborted; - /* package */ DfuServiceController(final Context context) { + /* package */ DfuServiceController(@NonNull final Context context) { mBroadcastManager = LocalBroadcastManager.getInstance(context); } diff --git a/dfu/src/main/java/no/nordicsemi/android/dfu/DfuServiceListenerHelper.java b/dfu/src/main/java/no/nordicsemi/android/dfu/DfuServiceListenerHelper.java index 516fe4e..4e1a0b1 100644 --- a/dfu/src/main/java/no/nordicsemi/android/dfu/DfuServiceListenerHelper.java +++ b/dfu/src/main/java/no/nordicsemi/android/dfu/DfuServiceListenerHelper.java @@ -26,23 +26,27 @@ import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; -import androidx.localbroadcastmanager.content.LocalBroadcastManager; import java.util.HashMap; import java.util.Locale; import java.util.Map; +import androidx.annotation.NonNull; +import androidx.localbroadcastmanager.content.LocalBroadcastManager; import no.nordicsemi.android.dfu.internal.scanner.BootloaderScanner; import no.nordicsemi.android.error.GattError; /** * A helper class that should be used to register listeners for DFU Service broadcast events. - * The {@link DfuProgressListener} should be registered to listen for DFU status updates and errors, while the {@link DfuLogListener} listener receives the log updates. - * Listeners may be registered for a specified device (given with device address) or for any device. Keep in mind, that while updating the SoftDevice using the buttonless update - * the device may change its address in the bootloader mode. - * - *

      Use {@link #registerProgressListener(Context, DfuProgressListener)} or {@link #registerLogListener(Context, DfuLogListener)} to register your listeners. Remember about unregistering them - * when your context is destroyed.

      + * The {@link DfuProgressListener} should be registered to listen for DFU status updates and errors, + * while the {@link DfuLogListener} listener receives the log updates. + * Listeners may be registered for a specified device (given with device address) or for any device. + * Keep in mind, that while updating the SoftDevice using the buttonless update the device may + * change its address in the bootloader mode. + *

      + * Use {@link #registerProgressListener(Context, DfuProgressListener)} or + * {@link #registerLogListener(Context, DfuLogListener)} to register your listeners. + * Remember about unregistering them when your context is destroyed. */ @SuppressWarnings({"WeakerAccess", "unused"}) public class DfuServiceListenerHelper { @@ -58,8 +62,10 @@ public class DfuServiceListenerHelper { } private void setLogListener(final String deviceAddress, final DfuLogListener listener) { - // When using the buttonless update and updating the SoftDevice the application will be removed to make space for the new SoftDevice. - // The new bootloader will afterwards advertise with the address incremented by 1. We need to make sure that the listener will receive also events from this device. + // When using the buttonless update and updating the SoftDevice the application will + // be removed to make space for the new SoftDevice. + // The new bootloader will afterwards advertise with the address incremented by 1. + // We need to make sure that the listener will receive also events from this device. mListeners.put(deviceAddress, listener); mListeners.put(getIncrementedAddress(deviceAddress), listener); // assuming the address is a valid BLE address } @@ -154,6 +160,8 @@ public class DfuServiceListenerHelper { return; final String action = intent.getAction(); + if (action == null) + return; switch (action) { case DfuBaseService.BROADCAST_PROGRESS: { @@ -268,11 +276,13 @@ public class DfuServiceListenerHelper { } /** - * Registers the {@link DfuProgressListener}. Registered listener will receive the progress events from the DFU service. - * @param context the application context - * @param listener the listener to register + * Registers the {@link DfuProgressListener}. + * Registered listener will receive the progress events from the DFU service. + * + * @param context the application context. + * @param listener the listener to register. */ - public static void registerProgressListener(final Context context, final DfuProgressListener listener) { + public static void registerProgressListener(@NonNull final Context context, @NonNull final DfuProgressListener listener) { if (mProgressBroadcastReceiver == null) { mProgressBroadcastReceiver = new ProgressBroadcastsReceiver(); @@ -285,12 +295,15 @@ public class DfuServiceListenerHelper { } /** - * Registers the {@link DfuProgressListener}. Registered listener will receive the progress events from the DFU service. - * @param context the application context - * @param listener the listener to register - * @param deviceAddress the address of the device to receive updates from (or null if any device) + * Registers the {@link DfuProgressListener}. Registered listener will receive the progress + * events from the DFU service. + * + * @param context the application context. + * @param listener the listener to register. + * @param deviceAddress the address of the device to receive updates from (or null if any device). */ - public static void registerProgressListener(final Context context, final DfuProgressListener listener, final String deviceAddress) { + public static void registerProgressListener(@NonNull final Context context, + @NonNull final DfuProgressListener listener, @NonNull final String deviceAddress) { if (mProgressBroadcastReceiver == null) { mProgressBroadcastReceiver = new ProgressBroadcastsReceiver(); @@ -304,10 +317,11 @@ public class DfuServiceListenerHelper { /** * Unregisters the previously registered progress listener. - * @param context the application context - * @param listener the listener to unregister + * + * @param context the application context. + * @param listener the listener to unregister. */ - public static void unregisterProgressListener(final Context context, final DfuProgressListener listener) { + public static void unregisterProgressListener(@NonNull final Context context, @NonNull final DfuProgressListener listener) { if (mProgressBroadcastReceiver != null) { final boolean empty = mProgressBroadcastReceiver.removeProgressListener(listener); @@ -320,10 +334,11 @@ public class DfuServiceListenerHelper { /** * Registers the {@link DfuLogListener}. Registered listener will receive the log events from the DFU service. - * @param context the application context - * @param listener the listener to register + * + * @param context the application context. + * @param listener the listener to register. */ - public static void registerLogListener(final Context context, final DfuLogListener listener) { + public static void registerLogListener(@NonNull final Context context, @NonNull final DfuLogListener listener) { if (mLogBroadcastReceiver == null) { mLogBroadcastReceiver = new LogBroadcastReceiver(); @@ -335,12 +350,15 @@ public class DfuServiceListenerHelper { } /** - * Registers the {@link DfuLogListener}. Registered listener will receive the log events from the DFU service. - * @param context the application context - * @param listener the listener to register - * @param deviceAddress the address of the device to receive updates from (or null if any device) + * Registers the {@link DfuLogListener}. Registered listener will receive the log events from + * the DFU service. + * + * @param context the application context. + * @param listener the listener to register. + * @param deviceAddress the address of the device to receive updates from (or null if any device). */ - public static void registerLogListener(final Context context, final DfuLogListener listener, final String deviceAddress) { + public static void registerLogListener(@NonNull final Context context, + @NonNull final DfuLogListener listener, @NonNull final String deviceAddress) { if (mLogBroadcastReceiver == null) { mLogBroadcastReceiver = new LogBroadcastReceiver(); @@ -353,10 +371,11 @@ public class DfuServiceListenerHelper { /** * Unregisters the previously registered log listener. - * @param context the application context - * @param listener the listener to unregister + * + * @param context the application context. + * @param listener the listener to unregister. */ - public static void unregisterLogListener(final Context context, final DfuLogListener listener) { + public static void unregisterLogListener(@NonNull final Context context, @NonNull final DfuLogListener listener) { if (mLogBroadcastReceiver != null) { final boolean empty = mLogBroadcastReceiver.removeLogListener(listener); @@ -367,7 +386,7 @@ public class DfuServiceListenerHelper { } } - private static String getIncrementedAddress(final String deviceAddress) { + private static String getIncrementedAddress(@NonNull final String deviceAddress) { final String firstBytes = deviceAddress.substring(0, 15); final String lastByte = deviceAddress.substring(15); // assuming that the device address is correct final String lastByteIncremented = String.format(Locale.US, "%02X", (Integer.valueOf(lastByte, 16) + BootloaderScanner.ADDRESS_DIFF) & 0xFF); diff --git a/dfu/src/main/java/no/nordicsemi/android/dfu/DfuServiceProvider.java b/dfu/src/main/java/no/nordicsemi/android/dfu/DfuServiceProvider.java index e5081d5..7278f76 100644 --- a/dfu/src/main/java/no/nordicsemi/android/dfu/DfuServiceProvider.java +++ b/dfu/src/main/java/no/nordicsemi/android/dfu/DfuServiceProvider.java @@ -25,6 +25,7 @@ package no.nordicsemi.android.dfu; import android.bluetooth.BluetoothGatt; import android.content.Intent; +import androidx.annotation.NonNull; import no.nordicsemi.android.dfu.internal.exception.DeviceDisconnectedException; import no.nordicsemi.android.dfu.internal.exception.DfuException; import no.nordicsemi.android.dfu.internal.exception.UploadAbortedException; @@ -34,7 +35,8 @@ import no.nordicsemi.android.dfu.internal.exception.UploadAbortedException; private boolean mPaused; private boolean mAborted; - /* package */ DfuService getServiceImpl(final Intent intent, final DfuBaseService service, final BluetoothGatt gatt) throws DfuException, DeviceDisconnectedException, UploadAbortedException { + DfuService getServiceImpl(@NonNull final Intent intent, @NonNull final DfuBaseService service, @NonNull final BluetoothGatt gatt) + throws DfuException, DeviceDisconnectedException, UploadAbortedException { try { mImpl = new ButtonlessDfuWithBondSharingImpl(intent, service); if (mImpl.isClientCompatible(intent, gatt)) @@ -56,7 +58,8 @@ import no.nordicsemi.android.dfu.internal.exception.UploadAbortedException; if (mImpl.isClientCompatible(intent, gatt)) return mImpl; - // Support for experimental Buttonless DFU Service from SDK 12. This feature must be explicitly enabled in the initiator. + // Support for experimental Buttonless DFU Service from SDK 12. + // This feature must be explicitly enabled in the initiator. final boolean enableUnsafeExperimentalButtonlessDfuService = intent.getBooleanExtra(DfuBaseService.EXTRA_UNSAFE_EXPERIMENTAL_BUTTONLESS_DFU, false); if (enableUnsafeExperimentalButtonlessDfuService) { mImpl = new ExperimentalButtonlessDfuImpl(intent, service); diff --git a/dfu/src/main/java/no/nordicsemi/android/dfu/DfuSettingsConstants.java b/dfu/src/main/java/no/nordicsemi/android/dfu/DfuSettingsConstants.java index f68ecf2..747e49a 100644 --- a/dfu/src/main/java/no/nordicsemi/android/dfu/DfuSettingsConstants.java +++ b/dfu/src/main/java/no/nordicsemi/android/dfu/DfuSettingsConstants.java @@ -24,6 +24,7 @@ package no.nordicsemi.android.dfu; import android.bluetooth.BluetoothGattCharacteristic; +@SuppressWarnings("DeprecatedIsStillUsed") public interface DfuSettingsConstants { /** * This property must contain a boolean value. diff --git a/dfu/src/main/java/no/nordicsemi/android/dfu/ExperimentalButtonlessDfuImpl.java b/dfu/src/main/java/no/nordicsemi/android/dfu/ExperimentalButtonlessDfuImpl.java index 3a8574f..67390d3 100644 --- a/dfu/src/main/java/no/nordicsemi/android/dfu/ExperimentalButtonlessDfuImpl.java +++ b/dfu/src/main/java/no/nordicsemi/android/dfu/ExperimentalButtonlessDfuImpl.java @@ -29,6 +29,7 @@ import android.content.Intent; import java.util.UUID; +import androidx.annotation.NonNull; import no.nordicsemi.android.dfu.internal.exception.DeviceDisconnectedException; import no.nordicsemi.android.dfu.internal.exception.DfuException; import no.nordicsemi.android.dfu.internal.exception.UploadAbortedException; @@ -41,21 +42,21 @@ import no.nordicsemi.android.dfu.internal.exception.UploadAbortedException; */ /* package */ class ExperimentalButtonlessDfuImpl extends ButtonlessDfuImpl { /** The UUID of the experimental Buttonless DFU service from SDK 12.x. */ - protected static final UUID DEFAULT_EXPERIMENTAL_BUTTONLESS_DFU_SERVICE_UUID = new UUID(0x8E400001F3154F60L, 0x9FB8838830DAEA50L); + static final UUID DEFAULT_EXPERIMENTAL_BUTTONLESS_DFU_SERVICE_UUID = new UUID(0x8E400001F3154F60L, 0x9FB8838830DAEA50L); /** The UUID of the experimental Buttonless DFU characteristic from SDK 12.x. */ - protected static final UUID DEFAULT_EXPERIMENTAL_BUTTONLESS_DFU_UUID = new UUID(0x8E400001F3154F60L, 0x9FB8838830DAEA50L); // the same as service + static final UUID DEFAULT_EXPERIMENTAL_BUTTONLESS_DFU_UUID = new UUID(0x8E400001F3154F60L, 0x9FB8838830DAEA50L); // the same as service - protected static UUID EXPERIMENTAL_BUTTONLESS_DFU_SERVICE_UUID = DEFAULT_EXPERIMENTAL_BUTTONLESS_DFU_SERVICE_UUID; - protected static UUID EXPERIMENTAL_BUTTONLESS_DFU_UUID = DEFAULT_EXPERIMENTAL_BUTTONLESS_DFU_UUID; + static UUID EXPERIMENTAL_BUTTONLESS_DFU_SERVICE_UUID = DEFAULT_EXPERIMENTAL_BUTTONLESS_DFU_SERVICE_UUID; + static UUID EXPERIMENTAL_BUTTONLESS_DFU_UUID = DEFAULT_EXPERIMENTAL_BUTTONLESS_DFU_UUID; private BluetoothGattCharacteristic mButtonlessDfuCharacteristic; - ExperimentalButtonlessDfuImpl(final Intent intent, final DfuBaseService service) { + ExperimentalButtonlessDfuImpl(@NonNull final Intent intent, @NonNull final DfuBaseService service) { super(intent, service); } @Override - public boolean isClientCompatible(final Intent intent, final BluetoothGatt gatt) { + public boolean isClientCompatible(@NonNull final Intent intent, @NonNull final BluetoothGatt gatt) { final BluetoothGattService dfuService = gatt.getService(EXPERIMENTAL_BUTTONLESS_DFU_SERVICE_UUID); if (dfuService == null) return false; @@ -82,7 +83,7 @@ import no.nordicsemi.android.dfu.internal.exception.UploadAbortedException; } @Override - public void performDfu(final Intent intent) throws DfuException, DeviceDisconnectedException, UploadAbortedException { + public void performDfu(@NonNull final Intent intent) throws DfuException, DeviceDisconnectedException, UploadAbortedException { logi("Experimental buttonless service found -> SDK 12.x"); super.performDfu(intent); } diff --git a/dfu/src/main/java/no/nordicsemi/android/dfu/FileType.java b/dfu/src/main/java/no/nordicsemi/android/dfu/FileType.java index def984c..e80ed54 100644 --- a/dfu/src/main/java/no/nordicsemi/android/dfu/FileType.java +++ b/dfu/src/main/java/no/nordicsemi/android/dfu/FileType.java @@ -2,6 +2,7 @@ package no.nordicsemi.android.dfu; import androidx.annotation.IntDef; +@SuppressWarnings("WeakerAccess") @IntDef(value = { DfuBaseService.TYPE_SOFT_DEVICE, DfuBaseService.TYPE_BOOTLOADER, diff --git a/dfu/src/main/java/no/nordicsemi/android/dfu/LegacyButtonlessDfuImpl.java b/dfu/src/main/java/no/nordicsemi/android/dfu/LegacyButtonlessDfuImpl.java index ba49993..85b84dd 100644 --- a/dfu/src/main/java/no/nordicsemi/android/dfu/LegacyButtonlessDfuImpl.java +++ b/dfu/src/main/java/no/nordicsemi/android/dfu/LegacyButtonlessDfuImpl.java @@ -31,6 +31,7 @@ import android.preference.PreferenceManager; import java.util.UUID; +import androidx.annotation.NonNull; import no.nordicsemi.android.dfu.internal.exception.DeviceDisconnectedException; import no.nordicsemi.android.dfu.internal.exception.DfuException; import no.nordicsemi.android.dfu.internal.exception.UploadAbortedException; @@ -40,21 +41,23 @@ import no.nordicsemi.android.dfu.internal.exception.UploadAbortedException; */ /* package */ class LegacyButtonlessDfuImpl extends BaseButtonlessDfuImpl { // UUIDs used by the DFU - protected static UUID DFU_SERVICE_UUID = LegacyDfuImpl.DEFAULT_DFU_SERVICE_UUID; - protected static UUID DFU_CONTROL_POINT_UUID = LegacyDfuImpl.DEFAULT_DFU_CONTROL_POINT_UUID; - protected static UUID DFU_VERSION_UUID = LegacyDfuImpl.DEFAULT_DFU_VERSION_UUID; + static UUID DFU_SERVICE_UUID = LegacyDfuImpl.DEFAULT_DFU_SERVICE_UUID; + static UUID DFU_CONTROL_POINT_UUID = LegacyDfuImpl.DEFAULT_DFU_CONTROL_POINT_UUID; + static UUID DFU_VERSION_UUID = LegacyDfuImpl.DEFAULT_DFU_VERSION_UUID; - private static final byte[] OP_CODE_ENTER_BOOTLOADER = new byte[] {0x01, 0x04}; + private static final byte[] OP_CODE_ENTER_BOOTLOADER = new byte[]{0x01, 0x04}; private BluetoothGattCharacteristic mControlPointCharacteristic; private int mVersion; - LegacyButtonlessDfuImpl(final Intent intent, final DfuBaseService service) { + LegacyButtonlessDfuImpl(@NonNull final Intent intent, @NonNull final DfuBaseService service) { super(intent, service); } + @SuppressWarnings("deprecation") @Override - public boolean isClientCompatible(final Intent intent, final BluetoothGatt gatt) throws DfuException, DeviceDisconnectedException, UploadAbortedException { + public boolean isClientCompatible(@NonNull final Intent intent, @NonNull final BluetoothGatt gatt) + throws DfuException, DeviceDisconnectedException, UploadAbortedException { final BluetoothGattService dfuService = gatt.getService(DFU_SERVICE_UUID); if (dfuService == null) return false; @@ -136,7 +139,7 @@ import no.nordicsemi.android.dfu.internal.exception.UploadAbortedException; } @Override - public void performDfu(final Intent intent) throws DfuException, DeviceDisconnectedException, UploadAbortedException { + public void performDfu(@NonNull final Intent intent) throws DfuException, DeviceDisconnectedException, UploadAbortedException { logw("Application with legacy buttonless update found"); // The service is connected to the application, not to the bootloader @@ -196,14 +199,16 @@ import no.nordicsemi.android.dfu.internal.exception.UploadAbortedException; /** * Reads the DFU Version characteristic if such exists. Otherwise it returns 0. * - * @param gatt the GATT device - * @param characteristic the characteristic to read - * @return a version number or 0 if not present on the bootloader - * @throws DeviceDisconnectedException - * @throws DfuException - * @throws UploadAbortedException + * @param gatt the GATT device. + * @param characteristic the characteristic to read. + * @return a version number or 0 if not present on the bootloader. + * @throws DeviceDisconnectedException Thrown when the device will disconnect in the middle of + * the transmission. + * @throws DfuException Thrown if DFU error occur. + * @throws UploadAbortedException Thrown if DFU operation was aborted by user. */ - private int readVersion(final BluetoothGatt gatt, final BluetoothGattCharacteristic characteristic) throws DeviceDisconnectedException, DfuException, UploadAbortedException { + private int readVersion(final BluetoothGatt gatt, final BluetoothGattCharacteristic characteristic) + throws DeviceDisconnectedException, DfuException, UploadAbortedException { if (!mConnected) throw new DeviceDisconnectedException("Unable to read version number: device disconnected"); if (mAborted) @@ -225,7 +230,7 @@ import no.nordicsemi.android.dfu.internal.exception.UploadAbortedException; // We have to wait until device receives a response or an error occur try { synchronized (mLock) { - while (((!mRequestCompleted || characteristic.getValue() == null ) && mConnected && mError == 0 && !mAborted) || mPaused) { + while (((!mRequestCompleted || characteristic.getValue() == null) && mConnected && mError == 0 && !mAborted) || mPaused) { mRequestCompleted = false; mLock.wait(); } diff --git a/dfu/src/main/java/no/nordicsemi/android/dfu/LegacyDfuImpl.java b/dfu/src/main/java/no/nordicsemi/android/dfu/LegacyDfuImpl.java index 424d404..9405d46 100644 --- a/dfu/src/main/java/no/nordicsemi/android/dfu/LegacyDfuImpl.java +++ b/dfu/src/main/java/no/nordicsemi/android/dfu/LegacyDfuImpl.java @@ -31,6 +31,8 @@ import android.os.SystemClock; import java.util.Locale; import java.util.UUID; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import no.nordicsemi.android.dfu.internal.ArchiveInputStream; import no.nordicsemi.android.dfu.internal.exception.DeviceDisconnectedException; import no.nordicsemi.android.dfu.internal.exception.DfuException; @@ -41,15 +43,15 @@ import no.nordicsemi.android.error.LegacyDfuError; /* package */ class LegacyDfuImpl extends BaseCustomDfuImpl { // UUIDs used by the DFU - protected static final UUID DEFAULT_DFU_SERVICE_UUID = new UUID(0x000015301212EFDEL, 0x1523785FEABCD123L); - protected static final UUID DEFAULT_DFU_CONTROL_POINT_UUID = new UUID(0x000015311212EFDEL, 0x1523785FEABCD123L); - protected static final UUID DEFAULT_DFU_PACKET_UUID = new UUID(0x000015321212EFDEL, 0x1523785FEABCD123L); - protected static final UUID DEFAULT_DFU_VERSION_UUID = new UUID(0x000015341212EFDEL, 0x1523785FEABCD123L); + static final UUID DEFAULT_DFU_SERVICE_UUID = new UUID(0x000015301212EFDEL, 0x1523785FEABCD123L); + static final UUID DEFAULT_DFU_CONTROL_POINT_UUID = new UUID(0x000015311212EFDEL, 0x1523785FEABCD123L); + static final UUID DEFAULT_DFU_PACKET_UUID = new UUID(0x000015321212EFDEL, 0x1523785FEABCD123L); + static final UUID DEFAULT_DFU_VERSION_UUID = new UUID(0x000015341212EFDEL, 0x1523785FEABCD123L); - protected static UUID DFU_SERVICE_UUID = DEFAULT_DFU_SERVICE_UUID; - protected static UUID DFU_CONTROL_POINT_UUID = DEFAULT_DFU_CONTROL_POINT_UUID; - protected static UUID DFU_PACKET_UUID = DEFAULT_DFU_PACKET_UUID; - protected static UUID DFU_VERSION_UUID = DEFAULT_DFU_VERSION_UUID; + static UUID DFU_SERVICE_UUID = DEFAULT_DFU_SERVICE_UUID; + static UUID DFU_CONTROL_POINT_UUID = DEFAULT_DFU_CONTROL_POINT_UUID; + static UUID DFU_PACKET_UUID = DEFAULT_DFU_PACKET_UUID; + static UUID DFU_VERSION_UUID = DEFAULT_DFU_VERSION_UUID; private static final int DFU_STATUS_SUCCESS = 1; // Operation codes and packets @@ -124,12 +126,12 @@ import no.nordicsemi.android.error.LegacyDfuError; } } - /* package */ LegacyDfuImpl(final Intent intent, final DfuBaseService service) { + /* package */ LegacyDfuImpl(@NonNull final Intent intent, @NonNull final DfuBaseService service) { super(intent, service); } @Override - public boolean isClientCompatible(final Intent intent, final BluetoothGatt gatt) { + public boolean isClientCompatible(@NonNull final Intent intent, @NonNull final BluetoothGatt gatt) { final BluetoothGattService dfuService = gatt.getService(DFU_SERVICE_UUID); if (dfuService == null) return false; @@ -162,7 +164,8 @@ import no.nordicsemi.android.error.LegacyDfuError; } @Override - public void performDfu(final Intent intent) throws DfuException, DeviceDisconnectedException, UploadAbortedException { + public void performDfu(@NonNull final Intent intent) + throws DfuException, DeviceDisconnectedException, UploadAbortedException { logw("Legacy DFU bootloader found"); mProgressInfo.setProgress(DfuBaseService.PROGRESS_STARTING); @@ -549,10 +552,13 @@ import no.nordicsemi.android.error.LegacyDfuError; /** * Sets number of data packets that will be send before the notification will be received. * - * @param data control point data packet - * @param value number of packets before receiving notification. If this value is 0, then the notification of packet receipt will be disabled by the DFU target. + * @param data control point data packet. + * @param value number of packets before receiving notification. + * If this value is 0, then the notification of packet receipt will be disabled + * by the DFU target. */ - private void setNumberOfPackets(final byte[] data, final int value) { + @SuppressWarnings("SameParameterValue") + private void setNumberOfPackets(@NonNull final byte[] data, final int value) { data[1] = (byte) (value & 0xFF); data[2] = (byte) ((value >> 8) & 0xFF); } @@ -561,12 +567,14 @@ import no.nordicsemi.android.error.LegacyDfuError; * Checks whether the response received is valid and returns the status code. * * @param response the response received from the DFU device. - * @param request the expected Op Code - * @return the status code - * @throws UnknownResponseException if response was not valid + * @param request the expected Op Code. + * @return The status code. + * @throws UnknownResponseException if response was not valid. */ - private int getStatusCode(final byte[] response, final int request) throws UnknownResponseException { - if (response == null || response.length != 3 || response[0] != OP_CODE_RESPONSE_CODE_KEY || response[1] != request || response[2] < 1 || response[2] > 6) + private int getStatusCode(@Nullable final byte[] response, final int request) + throws UnknownResponseException { + if (response == null || response.length != 3 || response[0] != OP_CODE_RESPONSE_CODE_KEY || + response[1] != request || response[2] < 1 || response[2] > 6) throw new UnknownResponseException("Invalid response received", response, OP_CODE_RESPONSE_CODE_KEY, request); return response[2]; } @@ -574,10 +582,10 @@ import no.nordicsemi.android.error.LegacyDfuError; /** * Returns the DFU Version characteristic if such exists. Otherwise it returns 0. * - * @param characteristic the characteristic to read - * @return a version number or 0 if not present on the bootloader + * @param characteristic the characteristic to read. + * @return a version number or 0 if not present on the bootloader. */ - private int readVersion(final BluetoothGattCharacteristic characteristic) { + private int readVersion(@Nullable final BluetoothGattCharacteristic characteristic) { // The value of this characteristic has been read before by LegacyButtonlessDfuImpl return characteristic != null ? characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT16, 0) : 0; } @@ -588,28 +596,34 @@ import no.nordicsemi.android.error.LegacyDfuError; * will be called or the device gets disconnected. * If connection state will change, or an error will occur, an exception will be thrown. * - * @param characteristic the characteristic to write to. Should be the DFU CONTROL POINT - * @param value the value to write to the characteristic - * @throws DeviceDisconnectedException - * @throws DfuException - * @throws UploadAbortedException + * @param characteristic the characteristic to write to. Should be the DFU CONTROL POINT. + * @param value the value to write to the characteristic. + * @throws DeviceDisconnectedException Thrown when the device will disconnect in the middle of + * the transmission. + * @throws DfuException Thrown if DFU error occur. + * @throws UploadAbortedException Thrown if DFU operation was aborted by user. */ - private void writeOpCode(final BluetoothGattCharacteristic characteristic, final byte[] value) throws DeviceDisconnectedException, DfuException, UploadAbortedException { + private void writeOpCode(@NonNull final BluetoothGattCharacteristic characteristic, @NonNull final byte[] value) + throws DeviceDisconnectedException, DfuException, UploadAbortedException { final boolean reset = value[0] == OP_CODE_RESET_KEY || value[0] == OP_CODE_ACTIVATE_AND_RESET_KEY; writeOpCode(characteristic, value, reset); } /** - * Writes the image size to the characteristic. This method is SYNCHRONOUS and wait until the {@link android.bluetooth.BluetoothGattCallback#onCharacteristicWrite(android.bluetooth.BluetoothGatt, android.bluetooth.BluetoothGattCharacteristic, int)} - * will be called or the device gets disconnected. If connection state will change, or an error will occur, an exception will be thrown. + * Writes the image size to the characteristic. This method is SYNCHRONOUS and wait until the + * {@link android.bluetooth.BluetoothGattCallback#onCharacteristicWrite(android.bluetooth.BluetoothGatt, android.bluetooth.BluetoothGattCharacteristic, int)} + * will be called or the device gets disconnected. If connection state will change, or an + * error will occur, an exception will be thrown. * - * @param characteristic the characteristic to write to. Should be the DFU PACKET - * @param imageSize the image size in bytes - * @throws DeviceDisconnectedException - * @throws DfuException - * @throws UploadAbortedException + * @param characteristic the characteristic to write to. Should be the DFU PACKET. + * @param imageSize the image size in bytes. + * @throws DeviceDisconnectedException Thrown when the device will disconnect in the middle of + * the transmission. + * @throws DfuException Thrown if DFU error occur. + * @throws UploadAbortedException Thrown if DFU operation was aborted by user. */ - private void writeImageSize(final BluetoothGattCharacteristic characteristic, final int imageSize) throws DeviceDisconnectedException, DfuException, + private void writeImageSize(@NonNull final BluetoothGattCharacteristic characteristic, final int imageSize) + throws DeviceDisconnectedException, DfuException, UploadAbortedException { mReceivedData = null; mError = 0; @@ -641,23 +655,27 @@ import no.nordicsemi.android.error.LegacyDfuError; /** *

      - * Writes the Soft Device, Bootloader and Application image sizes to the characteristic. Soft Device and Bootloader update is supported since Soft Device s110 v7.0.0. - * Sizes of SD, BL and App are uploaded as 3x UINT32 even though some of them may be 0s. F.e. if only App is being updated the data will be <0x00000000, 0x00000000, [App size]> - *

      + * Writes the Soft Device, Bootloader and Application image sizes to the characteristic. + * Soft Device and Bootloader update is supported since Soft Device s110 v7.0.0. + * Sizes of SD, BL and App are uploaded as 3x UINT32 even though some of them may be 0s. + * E.g. if only App is being updated the data will be <0x00000000, 0x00000000, [App size]> *

      - * This method is SYNCHRONOUS and wait until the {@link android.bluetooth.BluetoothGattCallback#onCharacteristicWrite(android.bluetooth.BluetoothGatt, android.bluetooth.BluetoothGattCharacteristic, int)} - * will be called or the device gets disconnected. If connection state will change, or an error will occur, an exception will be thrown. - *

      + * This method is SYNCHRONOUS and wait until the + * {@link android.bluetooth.BluetoothGattCallback#onCharacteristicWrite(android.bluetooth.BluetoothGatt, android.bluetooth.BluetoothGattCharacteristic, int)} + * will be called or the device gets disconnected. If connection state will change, or an + * error will occur, an exception will be thrown. * - * @param characteristic the characteristic to write to. Should be the DFU PACKET - * @param softDeviceImageSize the Soft Device image size in bytes - * @param bootloaderImageSize the Bootloader image size in bytes - * @param appImageSize the Application image size in bytes - * @throws DeviceDisconnectedException - * @throws DfuException - * @throws UploadAbortedException + * @param characteristic the characteristic to write to. Should be the DFU PACKET. + * @param softDeviceImageSize the Soft Device image size in bytes. + * @param bootloaderImageSize the Bootloader image size in bytes. + * @param appImageSize the Application image size in bytes. + * @throws DeviceDisconnectedException Thrown when the device will disconnect in the middle of + * the transmission. + * @throws DfuException Thrown if DFU error occur. + * @throws UploadAbortedException Thrown if DFU operation was aborted by user. */ - private void writeImageSize(final BluetoothGattCharacteristic characteristic, final int softDeviceImageSize, final int bootloaderImageSize, final int appImageSize) + private void writeImageSize(@NonNull final BluetoothGattCharacteristic characteristic, + final int softDeviceImageSize, final int bootloaderImageSize, final int appImageSize) throws DeviceDisconnectedException, DfuException, UploadAbortedException { mReceivedData = null; mError = 0; @@ -691,13 +709,16 @@ import no.nordicsemi.android.error.LegacyDfuError; /** * Sends Reset command to the target device to reset its state and restarts the DFU Service that will start again. - * @param gatt the GATT device - * @param intent intent used to start the service - * @throws DfuException - * @throws DeviceDisconnectedException - * @throws UploadAbortedException + * + * @param gatt the GATT device. + * @param intent intent used to start the service. + * @throws DeviceDisconnectedException Thrown when the device will disconnect in the middle of + * the transmission. + * @throws DfuException Thrown if DFU error occur. + * @throws UploadAbortedException Thrown if DFU operation was aborted by user. */ - private void resetAndRestart(final BluetoothGatt gatt, final Intent intent) throws DfuException, DeviceDisconnectedException, UploadAbortedException { + private void resetAndRestart(@NonNull final BluetoothGatt gatt, @NonNull final Intent intent) + throws DfuException, DeviceDisconnectedException, UploadAbortedException { mService.sendLogBroadcast(DfuBaseService.LOG_LEVEL_WARNING, "Last upload interrupted. Restarting device..."); // Send 'jump to bootloader command' (Start DFU) mProgressInfo.setProgress(DfuBaseService.PROGRESS_DISCONNECTING); diff --git a/dfu/src/main/java/no/nordicsemi/android/dfu/SecureDfuImpl.java b/dfu/src/main/java/no/nordicsemi/android/dfu/SecureDfuImpl.java index fa20f4c..00aa43e 100644 --- a/dfu/src/main/java/no/nordicsemi/android/dfu/SecureDfuImpl.java +++ b/dfu/src/main/java/no/nordicsemi/android/dfu/SecureDfuImpl.java @@ -36,6 +36,8 @@ import java.util.Locale; import java.util.UUID; import java.util.zip.CRC32; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import no.nordicsemi.android.dfu.internal.ArchiveInputStream; import no.nordicsemi.android.dfu.internal.exception.DeviceDisconnectedException; import no.nordicsemi.android.dfu.internal.exception.DfuException; @@ -49,13 +51,13 @@ import no.nordicsemi.android.error.SecureDfuError; @SuppressWarnings("JavaDoc") class SecureDfuImpl extends BaseCustomDfuImpl { // UUIDs used by the DFU - protected static final UUID DEFAULT_DFU_SERVICE_UUID = new UUID(0x0000FE5900001000L, 0x800000805F9B34FBL); // 16-bit UUID assigned by Bluetooth SIG - protected static final UUID DEFAULT_DFU_CONTROL_POINT_UUID = new UUID(0x8EC90001F3154F60L, 0x9FB8838830DAEA50L); - protected static final UUID DEFAULT_DFU_PACKET_UUID = new UUID(0x8EC90002F3154F60L, 0x9FB8838830DAEA50L); + static final UUID DEFAULT_DFU_SERVICE_UUID = new UUID(0x0000FE5900001000L, 0x800000805F9B34FBL); // 16-bit UUID assigned by Bluetooth SIG + static final UUID DEFAULT_DFU_CONTROL_POINT_UUID = new UUID(0x8EC90001F3154F60L, 0x9FB8838830DAEA50L); + static final UUID DEFAULT_DFU_PACKET_UUID = new UUID(0x8EC90002F3154F60L, 0x9FB8838830DAEA50L); - protected static UUID DFU_SERVICE_UUID = DEFAULT_DFU_SERVICE_UUID; - protected static UUID DFU_CONTROL_POINT_UUID = DEFAULT_DFU_CONTROL_POINT_UUID; - protected static UUID DFU_PACKET_UUID = DEFAULT_DFU_PACKET_UUID; + static UUID DFU_SERVICE_UUID = DEFAULT_DFU_SERVICE_UUID; + static UUID DFU_CONTROL_POINT_UUID = DEFAULT_DFU_CONTROL_POINT_UUID; + static UUID DFU_PACKET_UUID = DEFAULT_DFU_PACKET_UUID; private static final int DFU_STATUS_SUCCESS = 1; private static final int MAX_ATTEMPTS = 3; @@ -109,7 +111,8 @@ class SecureDfuImpl extends BaseCustomDfuImpl { // If so, update the number of bytes received mProgressInfo.setBytesReceived(offset); } else { - // Else, and only in case it was a PRN received, not the response for Calculate Checksum Request, stop sending data + // Else, and only in case it was a PRN received, not the response for + // Calculate Checksum Request, stop sending data if (mFirmwareUploadInProgress) { mFirmwareUploadInProgress = false; notifyLock(); @@ -121,8 +124,10 @@ class SecureDfuImpl extends BaseCustomDfuImpl { } default: { /* - * If the DFU target device is in invalid state (e.g. the Init Packet is required but has not been selected), the target will send DFU_STATUS_INVALID_STATE error - * for each firmware packet that was send. We are interested may ignore all but the first one. + * If the DFU target device is in invalid state (e.g. the Init Packet is + * required but has not been selected), the target will send + * DFU_STATUS_INVALID_STATE error for each firmware packet that was send. + * We are interested may ignore all but the first one. */ if (mRemoteErrorOccurred) break; @@ -142,12 +147,12 @@ class SecureDfuImpl extends BaseCustomDfuImpl { } } - SecureDfuImpl(final Intent intent, final DfuBaseService service) { + SecureDfuImpl(@NonNull final Intent intent, @NonNull final DfuBaseService service) { super(intent, service); } @Override - public boolean isClientCompatible(final Intent intent, final BluetoothGatt gatt) { + public boolean isClientCompatible(@NonNull final Intent intent, @NonNull final BluetoothGatt gatt) { final BluetoothGattService dfuService = gatt.getService(DFU_SERVICE_UUID); if (dfuService == null) return false; @@ -160,9 +165,14 @@ class SecureDfuImpl extends BaseCustomDfuImpl { } @Override - public boolean initialize(final Intent intent, final BluetoothGatt gatt, final int fileType, final InputStream firmwareStream, final InputStream initPacketStream) throws DfuException, DeviceDisconnectedException, UploadAbortedException { + public boolean initialize(@NonNull final Intent intent, @NonNull final BluetoothGatt gatt, + @FileType final int fileType, + @NonNull final InputStream firmwareStream, + @Nullable final InputStream initPacketStream) + throws DfuException, DeviceDisconnectedException, UploadAbortedException { if (initPacketStream == null) { - mService.sendLogBroadcast(DfuBaseService.LOG_LEVEL_ERROR, "The Init packet is required by this version DFU Bootloader"); + mService.sendLogBroadcast(DfuBaseService.LOG_LEVEL_ERROR, + "The Init packet is required by this version DFU Bootloader"); mService.terminateConnection(gatt, DfuBaseService.ERROR_INIT_PACKET_REQUIRED); return false; } @@ -191,7 +201,8 @@ class SecureDfuImpl extends BaseCustomDfuImpl { } @Override - public void performDfu(final Intent intent) throws DfuException, DeviceDisconnectedException, UploadAbortedException { + public void performDfu(@NonNull final Intent intent) + throws DfuException, DeviceDisconnectedException, UploadAbortedException { logw("Secure DFU bootloader found"); mProgressInfo.setProgress(DfuBaseService.PROGRESS_STARTING); @@ -215,7 +226,8 @@ class SecureDfuImpl extends BaseCustomDfuImpl { try { // Enable notifications enableCCCD(mControlPointCharacteristic, NOTIFICATIONS); - mService.sendLogBroadcast(DfuBaseService.LOG_LEVEL_APPLICATION, "Notifications enabled"); + mService.sendLogBroadcast(DfuBaseService.LOG_LEVEL_APPLICATION, + "Notifications enabled"); // Wait a second here before going further // Related: @@ -236,7 +248,8 @@ class SecureDfuImpl extends BaseCustomDfuImpl { mRemoteErrorOccurred = false; logw("Sending SD+BL failed. Trying to send App only"); - mService.sendLogBroadcast(DfuBaseService.LOG_LEVEL_WARNING, "Invalid system components. Trying to send application"); + mService.sendLogBroadcast(DfuBaseService.LOG_LEVEL_WARNING, + "Invalid system components. Trying to send application"); mFileType = DfuBaseService.TYPE_APPLICATION; // Set new content type in the ZIP Input Stream and update sizes of images @@ -263,7 +276,7 @@ class SecureDfuImpl extends BaseCustomDfuImpl { // We are ready with DFU, the device is disconnected, let's close it and finalize the operation. finalize(intent, false); - } catch (final UploadAbortedException e) { + } catch (@SuppressWarnings("CaughtExceptionImmediatelyRethrown") final UploadAbortedException e) { // In secure DFU there is currently not possible to reset the device to application mode, so... do nothing // The connection will be terminated in the DfuBaseService throw e; @@ -275,14 +288,16 @@ class SecureDfuImpl extends BaseCustomDfuImpl { } catch (final RemoteDfuException e) { final int error = DfuBaseService.ERROR_REMOTE_TYPE_SECURE | e.getErrorNumber(); loge(e.getMessage() + ": " + SecureDfuError.parse(error)); - mService.sendLogBroadcast(DfuBaseService.LOG_LEVEL_ERROR, String.format(Locale.US, "Remote DFU error: %s", SecureDfuError.parse(error))); + mService.sendLogBroadcast(DfuBaseService.LOG_LEVEL_ERROR, String.format(Locale.US, + "Remote DFU error: %s", SecureDfuError.parse(error))); // For the Extended Error more details can be obtained on some devices. if (e instanceof RemoteDfuExtendedErrorException) { final RemoteDfuExtendedErrorException ee = (RemoteDfuExtendedErrorException) e; final int extendedError = DfuBaseService.ERROR_REMOTE_TYPE_SECURE_EXTENDED | ee.getExtendedErrorNumber(); loge("Extended Error details: " + SecureDfuError.parseExtendedError(extendedError)); - mService.sendLogBroadcast(DfuBaseService.LOG_LEVEL_ERROR, "Details: " + SecureDfuError.parseExtendedError(extendedError) + " (Code = " + ee.getExtendedErrorNumber() + ")"); + mService.sendLogBroadcast(DfuBaseService.LOG_LEVEL_ERROR, + "Details: " + SecureDfuError.parseExtendedError(extendedError) + " (Code = " + ee.getExtendedErrorNumber() + ")"); mService.terminateConnection(gatt, extendedError | DfuBaseService.ERROR_REMOTE_MASK); } else { mService.terminateConnection(gatt, error | DfuBaseService.ERROR_REMOTE_MASK); @@ -293,29 +308,38 @@ class SecureDfuImpl extends BaseCustomDfuImpl { /** * This method does the following: *
        - *
      1. Selects the Command object - this Op Code returns the maximum acceptable size of a command object, and the offset and CRC32 of the command - * that is already stored in the device (in case the DFU was started in a previous connection and disconnected before it has finished).
      2. - *
      3. If the offset received is greater than 0 and less or equal to the size of the Init file that is to be sent, it will compare the + *
      4. Selects the Command object - this Op Code returns the maximum acceptable size of a + * command object, and the offset and CRC32 of the command that is already stored in the + * device (in case the DFU was started in a previous connection and disconnected before + * it has finished).
      5. + *
      6. If the offset received is greater than 0 and less or equal to the size of the + * Init file that is to be sent, it will compare the * received CRC with the local one and, if they match: *
          - *
        • If offset < init file size - it will continue sending the Init file from the point it stopped before,
        • - *
        • If offset == init file size - it will send Execute command to execute the Init file, as it may have not been executed before.
        • + *
        • If offset < init file size - it will continue sending the Init file from the + * point it stopped before,
        • + *
        • If offset == init file size - it will send Execute command to execute the + * Init file, as it may have not been executed before.
        • *
        *
      7. - *
      8. If the CRC don't match, or the received offset is greater then init file size, it creates the Command Object and sends the whole - * Init file as the previous one was different.
      9. + *
      10. If the CRC don't match, or the received offset is greater then init file size, + * it creates the Command Object and sends the whole Init file as the previous one was + * different.
      11. *
      - * Sending of the Init packet is done without using PRNs (Packet Receipt Notifications), so they are disabled prior to sending the data. + * Sending of the Init packet is done without using PRNs (Packet Receipt Notifications), + * so they are disabled prior to sending the data. * - * @param gatt the target GATT device - * @param allowResume true to allow resuming sending Init Packet. If false, it will be started again. + * @param gatt the target GATT device. + * @param allowResume true to allow resuming sending Init Packet. If false, it will be started + * again. * @throws RemoteDfuException * @throws DeviceDisconnectedException * @throws DfuException * @throws UploadAbortedException * @throws UnknownResponseException */ - private void sendInitPacket(final BluetoothGatt gatt, final boolean allowResume) throws RemoteDfuException, DeviceDisconnectedException, DfuException, UploadAbortedException, UnknownResponseException { + private void sendInitPacket(@NonNull final BluetoothGatt gatt, final boolean allowResume) + throws RemoteDfuException, DeviceDisconnectedException, DfuException, UploadAbortedException, UnknownResponseException { final CRC32 crc32 = new CRC32(); // Used to calculate CRC32 of the Init packet ObjectChecksum checksum; @@ -325,6 +349,7 @@ class SecureDfuImpl extends BaseCustomDfuImpl { final ObjectInfo info = selectObject(OBJECT_COMMAND); logi(String.format(Locale.US, "Command object info received (Max size = %d, Offset = %d, CRC = %08X)", info.maxSize, info.offset, info.CRC32)); mService.sendLogBroadcast(DfuBaseService.LOG_LEVEL_APPLICATION, String.format(Locale.US, "Command object info received (Max size = %d, Offset = %d, CRC = %08X)", info.maxSize, info.offset, info.CRC32)); + //noinspection StatementWithEmptyBody if (mInitPacketSizeInBytes > info.maxSize) { // Ignore this here. Later, after sending the 'Create object' command, DFU target will send an error if init packet is too large } @@ -447,33 +472,41 @@ class SecureDfuImpl extends BaseCustomDfuImpl { * This method does the following: *
        *
      1. Sets the Packet Receipt Notification to a value specified in the settings.
      2. - *
      3. Selects the Data object - this returns maximum single object size and the offset and CRC of the data already saved.
      4. - *
      5. If the offset received is greater than 0 it will calculate the CRC of the same number of bytes of the firmware to be sent. - * If the CRC match it will continue sending data. Otherwise, it will go back to the beginning of the last chunk, or to the beginning - * of the previous chunk assuming the last one was not executed before, and continue sending data from there.
      6. - *
      7. If the CRC and offset received match and the offset is equal to the firmware size, it will only send the Execute command.
      8. + *
      9. Selects the Data object - this returns maximum single object size and the offset + * and CRC of the data already saved.
      10. + *
      11. If the offset received is greater than 0 it will calculate the CRC of the same + * number of bytes of the firmware to be sent. If the CRC match it will continue sending data. + * Otherwise, it will go back to the beginning of the last chunk, or to the beginning + * of the previous chunk assuming the last one was not executed before, and continue + * sending data from there.
      12. + *
      13. If the CRC and offset received match and the offset is equal to the firmware size, + * it will only send the Execute command.
      14. *
      - * @param gatt the target GATT device + * @param gatt the target GATT device. * @throws RemoteDfuException * @throws DeviceDisconnectedException * @throws DfuException * @throws UploadAbortedException * @throws UnknownResponseException */ - private void sendFirmware(final BluetoothGatt gatt) throws RemoteDfuException, DeviceDisconnectedException, DfuException, UploadAbortedException, UnknownResponseException { + private void sendFirmware(final BluetoothGatt gatt) throws RemoteDfuException, + DeviceDisconnectedException, DfuException, UploadAbortedException, UnknownResponseException { // Send the number of packets of firmware before receiving a receipt notification int numberOfPacketsBeforeNotification = mPacketsBeforeNotification; if (numberOfPacketsBeforeNotification > 0) { setPacketReceiptNotifications(numberOfPacketsBeforeNotification); - mService.sendLogBroadcast(DfuBaseService.LOG_LEVEL_APPLICATION, "Packet Receipt Notif Req (Op Code = 2) sent (Value = " + numberOfPacketsBeforeNotification + ")"); + mService.sendLogBroadcast(DfuBaseService.LOG_LEVEL_APPLICATION, + "Packet Receipt Notif Req (Op Code = 2) sent (Value = " + numberOfPacketsBeforeNotification + ")"); } // We are ready to start sending the new firmware. logi("Setting object to Data (Op Code = 6, Type = 2)"); final ObjectInfo info = selectObject(OBJECT_DATA); - logi(String.format(Locale.US, "Data object info received (Max size = %d, Offset = %d, CRC = %08X)", info.maxSize, info.offset, info.CRC32)); - mService.sendLogBroadcast(DfuBaseService.LOG_LEVEL_APPLICATION, String.format(Locale.US, "Data object info received (Max size = %d, Offset = %d, CRC = %08X)", info.maxSize, info.offset, info.CRC32)); + logi(String.format(Locale.US, + "Data object info received (Max size = %d, Offset = %d, CRC = %08X)", info.maxSize, info.offset, info.CRC32)); + mService.sendLogBroadcast(DfuBaseService.LOG_LEVEL_APPLICATION, String.format(Locale.US, + "Data object info received (Max size = %d, Offset = %d, CRC = %08X)", info.maxSize, info.offset, info.CRC32)); mProgressInfo.setMaxObjectSizeInBytes(info.maxSize); // Number of chunks in which the data will be sent @@ -508,7 +541,8 @@ class SecureDfuImpl extends BaseCustomDfuImpl { if (crc == info.CRC32) { logi(info.offset + " bytes of data sent before, CRC match"); - mService.sendLogBroadcast(DfuBaseService.LOG_LEVEL_APPLICATION, info.offset + " bytes of data sent before, CRC match"); + mService.sendLogBroadcast(DfuBaseService.LOG_LEVEL_APPLICATION, + info.offset + " bytes of data sent before, CRC match"); mProgressInfo.setBytesSent(info.offset); mProgressInfo.setBytesReceived(info.offset); @@ -522,7 +556,8 @@ class SecureDfuImpl extends BaseCustomDfuImpl { } } else { logi(info.offset + " bytes sent before, CRC does not match"); - mService.sendLogBroadcast(DfuBaseService.LOG_LEVEL_WARNING, info.offset + " bytes sent before, CRC does not match"); + mService.sendLogBroadcast(DfuBaseService.LOG_LEVEL_WARNING, + info.offset + " bytes sent before, CRC does not match"); // The CRC of the current object is not correct. If there was another Data object sent before, its CRC must have been correct, // as it has been executed. Either way, we have to create the current object again. mProgressInfo.setBytesSent(bytesSentAndExecuted); @@ -531,7 +566,8 @@ class SecureDfuImpl extends BaseCustomDfuImpl { info.CRC32 = 0; // invalidate mFirmwareStream.reset(); logi("Resuming from byte " + info.offset + "..."); - mService.sendLogBroadcast(DfuBaseService.LOG_LEVEL_APPLICATION, "Resuming from byte " + info.offset + "..."); + mService.sendLogBroadcast(DfuBaseService.LOG_LEVEL_APPLICATION, + "Resuming from byte " + info.offset + "..."); } } catch (final IOException e) { loge("Error while reading firmware stream", e); @@ -554,10 +590,13 @@ class SecureDfuImpl extends BaseCustomDfuImpl { final int availableObjectSizeInBytes = mProgressInfo.getAvailableObjectSizeIsBytes(); logi("Creating Data object (Op Code = 1, Type = 2, Size = " + availableObjectSizeInBytes + ") (" + (currentChunk + 1) + "/" + chunkCount + ")"); writeCreateRequest(OBJECT_DATA, availableObjectSizeInBytes); - mService.sendLogBroadcast(DfuBaseService.LOG_LEVEL_APPLICATION, "Data object (" + (currentChunk + 1) + "/" + chunkCount + ") created"); - mService.sendLogBroadcast(DfuBaseService.LOG_LEVEL_APPLICATION, "Uploading firmware..."); + mService.sendLogBroadcast(DfuBaseService.LOG_LEVEL_APPLICATION, + "Data object (" + (currentChunk + 1) + "/" + chunkCount + ") created"); + mService.sendLogBroadcast(DfuBaseService.LOG_LEVEL_APPLICATION, + "Uploading firmware..."); } else { - mService.sendLogBroadcast(DfuBaseService.LOG_LEVEL_APPLICATION, "Resuming uploading firmware..."); + mService.sendLogBroadcast(DfuBaseService.LOG_LEVEL_APPLICATION, + "Resuming uploading firmware..."); resumeSendingData = false; } @@ -574,13 +613,15 @@ class SecureDfuImpl extends BaseCustomDfuImpl { logi("Sending Calculate Checksum command (Op Code = 3)"); final ObjectChecksum checksum = readChecksum(); logi(String.format(Locale.US, "Checksum received (Offset = %d, CRC = %08X)", checksum.offset, checksum.CRC32)); - mService.sendLogBroadcast(DfuBaseService.LOG_LEVEL_APPLICATION, String.format(Locale.US, "Checksum received (Offset = %d, CRC = %08X)", checksum.offset, checksum.CRC32)); + mService.sendLogBroadcast(DfuBaseService.LOG_LEVEL_APPLICATION, String.format(Locale.US, + "Checksum received (Offset = %d, CRC = %08X)", checksum.offset, checksum.CRC32)); // It may happen, that not all bytes that were sent were received by the remote device final int bytesLost = mProgressInfo.getBytesSent() - checksum.offset; if (bytesLost > 0) { logw(bytesLost + " bytes were lost!"); - mService.sendLogBroadcast(DfuBaseService.LOG_LEVEL_WARNING, bytesLost + " bytes were lost"); + mService.sendLogBroadcast(DfuBaseService.LOG_LEVEL_WARNING, + bytesLost + " bytes were lost"); try { // We have to reset the stream and read 'offset' number of bytes to recalculate the CRC @@ -595,7 +636,8 @@ class SecureDfuImpl extends BaseCustomDfuImpl { // To decrease the chance of loosing data next time let's set PRN to 1. This will make the update very long, but perhaps it will succeed. numberOfPacketsBeforeNotification = mPacketsBeforeNotification = 1; setPacketReceiptNotifications(numberOfPacketsBeforeNotification); - mService.sendLogBroadcast(DfuBaseService.LOG_LEVEL_APPLICATION, "Packet Receipt Notif Req (Op Code = 2) sent (Value = " + numberOfPacketsBeforeNotification + ")"); + mService.sendLogBroadcast(DfuBaseService.LOG_LEVEL_APPLICATION, + "Packet Receipt Notif Req (Op Code = 2) sent (Value = " + numberOfPacketsBeforeNotification + ")"); } // Calculate the CRC32 @@ -678,9 +720,10 @@ class SecureDfuImpl extends BaseCustomDfuImpl { * Sets number of data packets that will be send before the notification will be received. * * @param data control point data packet - * @param value number of packets before receiving notification. If this value is 0, then the notification of packet receipt will be disabled by the DFU target. + * @param value number of packets before receiving notification. If this value is 0, then the + * notification of packet receipt will be disabled by the DFU target. */ - private void setNumberOfPackets(final byte[] data, final int value) { + private void setNumberOfPackets(@SuppressWarnings("SameParameterValue") @NonNull final byte[] data, final int value) { data[1] = (byte) (value & 0xFF); data[2] = (byte) ((value >> 8) & 0xFF); } @@ -691,7 +734,7 @@ class SecureDfuImpl extends BaseCustomDfuImpl { * @param data control point data packet * @param value Object size in bytes. */ - private void setObjectSize(final byte[] data, final int value) { + private void setObjectSize(@NonNull final byte[] data, final int value) { data[2] = (byte) (value & 0xFF); data[3] = (byte) ((value >> 8) & 0xFF); data[4] = (byte) ((value >> 16) & 0xFF); @@ -699,20 +742,23 @@ class SecureDfuImpl extends BaseCustomDfuImpl { } /** - * Sets the number of packets that needs to be sent to receive the Packet Receipt Notification. Value 0 disables PRNs. - * By default this is disabled. The PRNs may be used to send both the Data and Command object, but this Secure DFU implementation - * can handle them only during Data transfer.
      - * The intention of having PRNs is to make sure the outgoing BLE buffer is not getting overflown. The PRN will be sent after sending all - * packets from the queue. + * Sets the number of packets that needs to be sent to receive the Packet Receipt Notification. + * Value 0 disables PRNs. By default this is disabled. The PRNs may be used to send both the + * Data and Command object, but this Secure DFU implementation can handle them only during Data transfer. + *

      + * The intention of having PRNs is to make sure the outgoing BLE buffer is not getting overflown. + * The PRN will be sent after sending all packets from the queue. * - * @param number number of packets required before receiving a Packet Receipt Notification + * @param number number of packets required before receiving a Packet Receipt Notification. * @throws DfuException * @throws DeviceDisconnectedException * @throws UploadAbortedException * @throws UnknownResponseException * @throws RemoteDfuException thrown when the returned status code is not equal to {@link #DFU_STATUS_SUCCESS} */ - private void setPacketReceiptNotifications(final int number) throws DfuException, DeviceDisconnectedException, UploadAbortedException, UnknownResponseException, RemoteDfuException { + private void setPacketReceiptNotifications(final int number) + throws DfuException, DeviceDisconnectedException, UploadAbortedException, + UnknownResponseException, RemoteDfuException { if (!mConnected) throw new DeviceDisconnectedException("Unable to read Checksum: device disconnected"); @@ -736,27 +782,31 @@ class SecureDfuImpl extends BaseCustomDfuImpl { * will be called or the device gets disconnected. * If connection state will change, or an error will occur, an exception will be thrown. * - * @param characteristic the characteristic to write to. Should be the DFU CONTROL POINT - * @param value the value to write to the characteristic + * @param characteristic the characteristic to write to. Should be the DFU CONTROL POINT. + * @param value the value to write to the characteristic. * @throws DeviceDisconnectedException * @throws DfuException * @throws UploadAbortedException */ - private void writeOpCode(final BluetoothGattCharacteristic characteristic, final byte[] value) throws DeviceDisconnectedException, DfuException, UploadAbortedException { + private void writeOpCode(@NonNull final BluetoothGattCharacteristic characteristic, @NonNull final byte[] value) + throws DeviceDisconnectedException, DfuException, UploadAbortedException { writeOpCode(characteristic, value, false); } /** * Writes Create Object request providing the type and size of the object. - * @param type {@link #OBJECT_COMMAND} or {@link #OBJECT_DATA} - * @param size size of the object or current part of the object + * + * @param type {@link #OBJECT_COMMAND} or {@link #OBJECT_DATA}. + * @param size size of the object or current part of the object. * @throws DeviceDisconnectedException * @throws DfuException * @throws UploadAbortedException * @throws RemoteDfuException thrown when the returned status code is not equal to {@link #DFU_STATUS_SUCCESS} * @throws UnknownResponseException */ - private void writeCreateRequest(final int type, final int size) throws DeviceDisconnectedException, DfuException, UploadAbortedException, RemoteDfuException, UnknownResponseException { + private void writeCreateRequest(final int type, final int size) + throws DeviceDisconnectedException, DfuException, UploadAbortedException, RemoteDfuException, + UnknownResponseException { if (!mConnected) throw new DeviceDisconnectedException("Unable to create object: device disconnected"); @@ -773,15 +823,19 @@ class SecureDfuImpl extends BaseCustomDfuImpl { } /** - * Selects the current object and reads its metadata. The object info contains the max object size, and the offset and CRC32 of the whole object until now. + * Selects the current object and reads its metadata. The object info contains the max object + * size, and the offset and CRC32 of the whole object until now. * - * @return object info + * @return object info. * @throws DeviceDisconnectedException * @throws DfuException * @throws UploadAbortedException - * @throws RemoteDfuException thrown when the returned status code is not equal to {@link #DFU_STATUS_SUCCESS} + * @throws RemoteDfuException thrown when the returned status code is not equal to + * {@link #DFU_STATUS_SUCCESS}. */ - private ObjectInfo selectObject(final int type) throws DeviceDisconnectedException, DfuException, UploadAbortedException, RemoteDfuException, UnknownResponseException { + private ObjectInfo selectObject(final int type) + throws DeviceDisconnectedException, DfuException, UploadAbortedException, + RemoteDfuException, UnknownResponseException { if (!mConnected) throw new DeviceDisconnectedException("Unable to read object info: device disconnected"); @@ -803,15 +857,18 @@ class SecureDfuImpl extends BaseCustomDfuImpl { } /** - * Sends the Calculate Checksum request. As a response a notification will be sent with current offset and CRC32 of the current object. + * Sends the Calculate Checksum request. As a response a notification will be sent with current + * offset and CRC32 of the current object. * - * @return requested object info + * @return requested object info. * @throws DeviceDisconnectedException * @throws DfuException * @throws UploadAbortedException - * @throws RemoteDfuException thrown when the returned status code is not equal to {@link #DFU_STATUS_SUCCESS} + * @throws RemoteDfuException thrown when the returned status code is not equal to + * {@link #DFU_STATUS_SUCCESS}. */ - private ObjectChecksum readChecksum() throws DeviceDisconnectedException, DfuException, UploadAbortedException, RemoteDfuException, UnknownResponseException { + private ObjectChecksum readChecksum() throws DeviceDisconnectedException, DfuException, + UploadAbortedException, RemoteDfuException, UnknownResponseException { if (!mConnected) throw new DeviceDisconnectedException("Unable to read Checksum: device disconnected"); @@ -833,15 +890,18 @@ class SecureDfuImpl extends BaseCustomDfuImpl { /** * Sends the Execute operation code and awaits for a return notification containing status code. * The Execute command will confirm the last chunk of data or the last command that was sent. - * Creating the same object again, instead of executing it allows to retransmitting it in case of a CRC error. + * Creating the same object again, instead of executing it allows to retransmitting it in case + * of a CRC error. * * @throws DfuException * @throws DeviceDisconnectedException * @throws UploadAbortedException * @throws UnknownResponseException - * @throws RemoteDfuException thrown when the returned status code is not equal to {@link #DFU_STATUS_SUCCESS} + * @throws RemoteDfuException thrown when the returned status code is not equal to + * {@link #DFU_STATUS_SUCCESS}. */ - private void writeExecute() throws DfuException, DeviceDisconnectedException, UploadAbortedException, UnknownResponseException, RemoteDfuException { + private void writeExecute() throws DfuException, DeviceDisconnectedException, + UploadAbortedException, UnknownResponseException, RemoteDfuException { if (!mConnected) throw new DeviceDisconnectedException("Unable to read Checksum: device disconnected"); @@ -866,14 +926,19 @@ class SecureDfuImpl extends BaseCustomDfuImpl { * so such update is forbidden as it would brick the device. * * See: https://github.com/NordicSemiconductor/Android-DFU-Library/issues/86 - * @param allowRetry if true service will retry to send the execute command in case of INVALID_OBJECT error. + * + * @param allowRetry if true service will retry to send the execute command in case of + * INVALID_OBJECT error. * @throws DfuException * @throws DeviceDisconnectedException * @throws UploadAbortedException * @throws UnknownResponseException - * @throws RemoteDfuException thrown when the returned status code is not equal to {@link #DFU_STATUS_SUCCESS} + * @throws RemoteDfuException thrown when the returned status code is not equal to + * {@link #DFU_STATUS_SUCCESS}. */ - private void writeExecute(final boolean allowRetry) throws DfuException, DeviceDisconnectedException, UploadAbortedException, UnknownResponseException, RemoteDfuException { + private void writeExecute(final boolean allowRetry) + throws DfuException, DeviceDisconnectedException, UploadAbortedException, + UnknownResponseException, RemoteDfuException { try { writeExecute(); } catch (final RemoteDfuException e) { @@ -895,11 +960,11 @@ class SecureDfuImpl extends BaseCustomDfuImpl { } private class ObjectInfo extends ObjectChecksum { - protected int maxSize; + int maxSize; } private class ObjectChecksum { - protected int offset; - protected int CRC32; + int offset; + int CRC32; } } diff --git a/dfu/src/main/java/no/nordicsemi/android/dfu/UuidHelper.java b/dfu/src/main/java/no/nordicsemi/android/dfu/UuidHelper.java index b0d4b3b..e208b3b 100644 --- a/dfu/src/main/java/no/nordicsemi/android/dfu/UuidHelper.java +++ b/dfu/src/main/java/no/nordicsemi/android/dfu/UuidHelper.java @@ -26,9 +26,11 @@ import android.content.Intent; import android.os.ParcelUuid; import android.os.Parcelable; +import androidx.annotation.NonNull; + /* package */ class UuidHelper { - /* package */ static void assignCustomUuids(final Intent intent) { + /* package */ static void assignCustomUuids(@NonNull final Intent intent) { // Added in SDK 4.3.0. Legacy DFU and Legacy bootloader share the same UUIDs. Parcelable[] uuids = intent.getParcelableArrayExtra(DfuBaseService.EXTRA_CUSTOM_UUIDS_FOR_LEGACY_DFU); if (uuids != null && uuids.length == 4) { diff --git a/dfu/src/main/java/no/nordicsemi/android/dfu/internal/ArchiveInputStream.java b/dfu/src/main/java/no/nordicsemi/android/dfu/internal/ArchiveInputStream.java index a88375e..fbb03fc 100644 --- a/dfu/src/main/java/no/nordicsemi/android/dfu/internal/ArchiveInputStream.java +++ b/dfu/src/main/java/no/nordicsemi/android/dfu/internal/ArchiveInputStream.java @@ -22,7 +22,6 @@ package no.nordicsemi.android.dfu.internal; -import androidx.annotation.NonNull; import android.util.Log; import com.google.gson.Gson; @@ -38,6 +37,7 @@ import java.util.zip.CRC32; import java.util.zip.ZipEntry; import java.util.zip.ZipInputStream; +import androidx.annotation.NonNull; import no.nordicsemi.android.dfu.DfuBaseService; import no.nordicsemi.android.dfu.internal.manifest.FileInfo; import no.nordicsemi.android.dfu.internal.manifest.Manifest; @@ -45,15 +45,22 @@ import no.nordicsemi.android.dfu.internal.manifest.ManifestFile; import no.nordicsemi.android.dfu.internal.manifest.SoftDeviceBootloaderFileInfo; /** - *

      Reads the firmware files from the a ZIP file. The ZIP file must be either created using the nrf utility tool, available together with Master Control Panel v3.8.0+, - * or follow the backward compatibility syntax: must contain only files with names: application.hex/bin, softdevice.hex/dat or bootloader.hex/bin, optionally also application.dat - * and/or system.dat with init packets.

      - *

      The ArchiveInputStream will read only files with types specified by types parameter of the constructor.

      + *

      + * Reads the firmware files from the a ZIP file. The ZIP file must be either created using the + * nrf util, + * or follow the backward compatibility syntax: must contain only files with names: + * application.hex/bin, softdevice.hex/dat or bootloader.hex/bin, optionally also application.dat + * and/or system.dat with init packets. + *

      + * The ArchiveInputStream will read only files with types specified by types parameter of + * the constructor. */ public class ArchiveInputStream extends InputStream { private static final String TAG = "DfuArchiveInputStream"; - /** The name of the manifest file is fixed. */ + /** + * The name of the manifest file is fixed. + */ private static final String MANIFEST = "manifest.json"; // Those file names are for backwards compatibility mode private static final String SOFTDEVICE_HEX = "softdevice.hex"; @@ -67,7 +74,10 @@ public class ArchiveInputStream extends InputStream { private final ZipInputStream zipInputStream; - /** Contains bytes arrays with BIN files. HEX files are converted to BIN before being added to this map. */ + /** + * Contains bytes arrays with BIN files. HEX files are converted to BIN before being + * added to this map. + */ private Map entries; private Manifest manifest; private CRC32 crc32; @@ -91,12 +101,13 @@ public class ArchiveInputStream extends InputStream { /** *

      - * The ArchiveInputStream read HEX or BIN files from the Zip stream. It may skip some of them, depending on the value of the types parameter. - * This is useful if the DFU service wants to send the Soft Device and Bootloader only, and then the Application in the following connection, despite - * the ZIP file contains all 3 HEX/BIN files. + * The ArchiveInputStream read HEX or BIN files from the Zip stream. It may skip some of them, + * depending on the value of the types parameter. This is useful if the DFU service wants to + * send the Soft Device and Bootloader only, and then the Application in the following connection, + * despite the ZIP file contains all 3 HEX/BIN files. * When types is equal to {@link DfuBaseService#TYPE_AUTO} all present files are read. - *

      - *

      Use bit combination of the following types:

      + *

      + * Use bit combination of the following types: *

        *
      • {@link DfuBaseService#TYPE_SOFT_DEVICE}
      • *
      • {@link DfuBaseService#TYPE_BOOTLOADER}
      • @@ -104,15 +115,15 @@ public class ArchiveInputStream extends InputStream { *
      • {@link DfuBaseService#TYPE_AUTO}
      • *
      * - * @param stream - * the Zip Input Stream - * @param mbrSize - * The size of the MRB segment (Master Boot Record) on the device. The parser will cut data from addresses below that number from all HEX files. - * @param types - * File types that are to be read from the ZIP. Use {@link DfuBaseService#TYPE_APPLICATION} etc. - * @throws java.io.IOException + * @param stream the Zip Input Stream + * @param mbrSize The size of the MRB segment (Master Boot Record) on the device. + * The parser will cut data from addresses below that number from all HEX files. + * @param types File types that are to be read from the ZIP. Use + * {@link DfuBaseService#TYPE_APPLICATION} etc. + * @throws java.io.IOException Thrown in case of an invalid ZIP file. */ - public ArchiveInputStream(final InputStream stream, final int mbrSize, final int types) throws IOException { + public ArchiveInputStream(final InputStream stream, final int mbrSize, final int types) + throws IOException { this.zipInputStream = new ZipInputStream(stream); this.crc32 = new CRC32(); @@ -262,12 +273,15 @@ public class ArchiveInputStream extends InputStream { /** * Reads all files into byte arrays. * Here we don't know whether the ZIP file is valid. - * - * The ZIP file is valid when contains a 'manifest.json' file and all BIN and DAT files that are specified in the manifest. - * - * For backwards compatibility ArchiveInputStream supports also ZIP archives without 'manifest.json' file - * but than it MUST include at least one of the following files: softdevice.bin/hex, bootloader.bin/hex, application.bin/hex. - * To support the init packet such ZIP file should contain also application.dat and/or system.dat (with the CRC16 of a SD, BL or SD+BL together). + *

      + * The ZIP file is valid when contains a 'manifest.json' file and all BIN and DAT files that + * are specified in the manifest. + *

      + * For backwards compatibility ArchiveInputStream supports also ZIP archives without + * 'manifest.json' file but than it MUST include at least one of the following files: + * softdevice.bin/hex, bootloader.bin/hex, application.bin/hex. + * To support the init packet such ZIP file should contain also application.dat and/or system.dat + * (with the CRC16 of a SD, BL or SD+BL together). */ private void parseZip(final int mbrSize) throws IOException { final byte[] buffer = new byte[1024]; @@ -307,7 +321,8 @@ public class ArchiveInputStream extends InputStream { // Some validation if (entries.isEmpty()) { - throw new FileNotFoundException("No files found in the ZIP. Check if the URI provided is valid and the ZIP contains required files on root level, not in a directory."); + throw new FileNotFoundException("No files found in the ZIP. Check if the URI provided is " + + "valid and the ZIP contains required files on root level, not in a directory."); } if (manifestData != null) { @@ -319,7 +334,8 @@ public class ArchiveInputStream extends InputStream { "to your proguard rules?"); } } else { - Log.w(TAG, "Manifest not found in the ZIP. It is recommended to use a distribution file created with: https://github.com/NordicSemiconductor/pc-nrfutil/ (for Legacy DFU use version 0.5.x)"); + Log.w(TAG, "Manifest not found in the ZIP. It is recommended to use a distribution " + + "file created with: https://github.com/NordicSemiconductor/pc-nrfutil/ (for Legacy DFU use version 0.5.x)"); } } @@ -381,6 +397,7 @@ public class ArchiveInputStream extends InputStream { /** * Marks the current position in the stream. The parameter is ignored. + * * @param readlimit this parameter is ignored, can be anything */ @Override @@ -429,6 +446,7 @@ public class ArchiveInputStream extends InputStream { /** * Returns the CRC32 of the part of the firmware that was already read. + * * @return the CRC */ public long getCrc32() { @@ -436,14 +454,17 @@ public class ArchiveInputStream extends InputStream { } /** - * Returns the content type based on the content of the ZIP file. The content type may be truncated using {@link #setContentType(int)}. + * Returns the content type based on the content of the ZIP file. The content type may be + * truncated using {@link #setContentType(int)}. * - * @return a bit field of {@link DfuBaseService#TYPE_SOFT_DEVICE TYPE_SOFT_DEVICE}, {@link DfuBaseService#TYPE_BOOTLOADER TYPE_BOOTLOADER} and {@link DfuBaseService#TYPE_APPLICATION - * TYPE_APPLICATION} + * @return A bit field of {@link DfuBaseService#TYPE_SOFT_DEVICE TYPE_SOFT_DEVICE}, + * {@link DfuBaseService#TYPE_BOOTLOADER TYPE_BOOTLOADER} and + * {@link DfuBaseService#TYPE_APPLICATION TYPE_APPLICATION} */ public int getContentType() { type = 0; - // In Secure DFU the softDeviceSize and bootloaderSize may be 0 if both are in the ZIP file. The size of each part is embedded in the Init packet. + // In Secure DFU the softDeviceSize and bootloaderSize may be 0 if both are in the ZIP file. + // The size of each part is embedded in the Init packet. if (softDeviceAndBootloaderBytes != null) type |= DfuBaseService.TYPE_SOFT_DEVICE | DfuBaseService.TYPE_BOOTLOADER; // In Legacy DFU the size of each of these parts was given in the manifest file. @@ -457,11 +478,11 @@ public class ArchiveInputStream extends InputStream { } /** - * Truncates the current content type. May be used to hide some files, e.g. to send Soft Device and Bootloader without Application or only the Application. + * Truncates the current content type. May be used to hide some files, e.g. to send Soft Device + * and Bootloader without Application or only the Application. * - * @param type - * the new type - * @return the final type after truncating + * @param type the new type. + * @return The final type after truncating. */ public int setContentType(final int type) { this.type = type; @@ -497,9 +518,10 @@ public class ArchiveInputStream extends InputStream { } /** - * Sets the currentSource to the new file or to null if the last file has been transmitted. + * Sets the currentSource to the new file or to null if the last file has been + * transmitted. * - * @return the new source, the same as {@link #currentSource} + * @return The new source, the same as {@link #currentSource}. */ private byte[] startNextFile() { byte[] ret; @@ -515,12 +537,15 @@ public class ArchiveInputStream extends InputStream { } /** - * Returns the number of bytes that has not been read yet. This value includes only firmwares matching the content type set by the construcotor or the {@link #setContentType(int)} method. + * Returns the number of bytes that has not been read yet. This value includes only + * firmwares matching the content type set by the constructor or the + * {@link #setContentType(int)} method. */ @Override public int available() { - // In Secure DFU softdevice and bootloader sizes are not provided in the Init file (they are encoded inside the Init file instead). - // The service doesn't send those sizes, not the whole size of the firmware separately, like it was done in the Legacy DFU. + // In Secure DFU softdevice and bootloader sizes are not provided in the Init file + // (they are encoded inside the Init file instead). The service doesn't send those sizes, + // not the whole size of the firmware separately, like it was done in the Legacy DFU. // This method then is just used to log file size. // In case of SD+BL in Secure DFU: @@ -533,42 +558,54 @@ public class ArchiveInputStream extends InputStream { } /** - * Returns the total size of the SoftDevice firmware. In case the firmware was given as a HEX, this method returns the size of the BIN content of the file. - * @return the size of the SoftDevice firmware (BIN part) + * Returns the total size of the SoftDevice firmware. In case the firmware was given as a HEX, + * this method returns the size of the BIN content of the file. + * + * @return The size of the SoftDevice firmware (BIN part). */ public int softDeviceImageSize() { return (type & DfuBaseService.TYPE_SOFT_DEVICE) > 0 ? softDeviceSize : 0; } /** - * Returns the total size of the Bootloader firmware. In case the firmware was given as a HEX, this method returns the size of the BIN content of the file. - * @return the size of the Bootloader firmware (BIN part) + * Returns the total size of the Bootloader firmware. In case the firmware was given as a HEX, + * this method returns the size of the BIN content of the file. + * + * @return The size of the Bootloader firmware (BIN part). */ public int bootloaderImageSize() { return (type & DfuBaseService.TYPE_BOOTLOADER) > 0 ? bootloaderSize : 0; } /** - * Returns the total size of the Application firmware. In case the firmware was given as a HEX, this method returns the size of the BIN content of the file. - * @return the size of the Application firmware (BIN part) + * Returns the total size of the Application firmware. In case the firmware was given as a HEX, + * this method returns the size of the BIN content of the file. + * + * @return The size of the Application firmware (BIN part). */ public int applicationImageSize() { return (type & DfuBaseService.TYPE_APPLICATION) > 0 ? applicationSize : 0; } /** - * Returns the content of the init file for SoftDevice and/or Bootloader. When both SoftDevice and Bootloader are present in the ZIP file (as two files using the compatibility mode - * or as one file using the new Distribution packet) the system init contains validation data for those two files combined (e.g. the CRC value). This method may return - * null if there is no SoftDevice nor Bootloader in the ZIP or the DAT file is not present there. - * @return the content of the init packet for SoftDevice and/or Bootloader + * Returns the content of the init file for SoftDevice and/or Bootloader. When both SoftDevice + * and Bootloader are present in the ZIP file (as two files using the compatibility mode + * or as one file using the new Distribution packet) the system init contains validation data + * for those two files combined (e.g. the CRC value). This method may return + * null if there is no SoftDevice nor Bootloader in the ZIP or the DAT file is + * not present there. + * + * @return The content of the init packet for SoftDevice and/or Bootloader. */ public byte[] getSystemInit() { return systemInitBytes; } /** - * Returns the content of the init file for the Application or null if no application file in the ZIP, or the DAT file is not provided. - * @return the content of the init packet for Application + * Returns the content of the init file for the Application or null if no + * application file in the ZIP, or the DAT file is not provided. + * + * @return The content of the init packet for Application. */ public byte[] getApplicationInit() { return applicationInitBytes; @@ -576,9 +613,12 @@ public class ArchiveInputStream extends InputStream { /** * This method returns true if the content of the ZIP file may be sent only using Secure DFU. - * The reason may be that the ZIP contains a single bin file with SD and/or BL together with App, which has to be sent in a single connection. - * Sizes of each component are not given explicitly in the Manifest (even if they are, they are ignored). They are hidden in the Init Packet instead. - * @return true if the content of this ZIP may only be sent using Secure DFU. + * The reason may be that the ZIP contains a single bin file with SD and/or BL together with + * App, which has to be sent in a single connection. + * Sizes of each component are not given explicitly in the Manifest (even if they are, + * they are ignored). They are hidden in the Init Packet instead. + * + * @return True if the content of this ZIP may only be sent using Secure DFU. */ public boolean isSecureDfuRequired() { return manifest != null && manifest.isSecureDfuRequired(); diff --git a/dfu/src/main/java/no/nordicsemi/android/dfu/internal/manifest/Manifest.java b/dfu/src/main/java/no/nordicsemi/android/dfu/internal/manifest/Manifest.java index 05d8eba..fa17deb 100644 --- a/dfu/src/main/java/no/nordicsemi/android/dfu/internal/manifest/Manifest.java +++ b/dfu/src/main/java/no/nordicsemi/android/dfu/internal/manifest/Manifest.java @@ -24,6 +24,7 @@ package no.nordicsemi.android.dfu.internal.manifest; import com.google.gson.annotations.SerializedName; +@SuppressWarnings("unused") public class Manifest { private FileInfo application; private FileInfo bootloader; diff --git a/dfu/src/main/java/no/nordicsemi/android/dfu/internal/manifest/ManifestFile.java b/dfu/src/main/java/no/nordicsemi/android/dfu/internal/manifest/ManifestFile.java index 92ce52c..8961b7b 100644 --- a/dfu/src/main/java/no/nordicsemi/android/dfu/internal/manifest/ManifestFile.java +++ b/dfu/src/main/java/no/nordicsemi/android/dfu/internal/manifest/ManifestFile.java @@ -22,6 +22,7 @@ package no.nordicsemi.android.dfu.internal.manifest; +@SuppressWarnings("unused") public class ManifestFile { private Manifest manifest; From 8e2681fdff6fb4b42c2405546e95ac3e205eacab Mon Sep 17 00:00:00 2001 From: Aleksander Nowakowski Date: Wed, 20 Feb 2019 12:52:43 +0100 Subject: [PATCH 08/13] Bug fixed --- .../no/nordicsemi/android/dfu/internal/ArchiveInputStream.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dfu/src/main/java/no/nordicsemi/android/dfu/internal/ArchiveInputStream.java b/dfu/src/main/java/no/nordicsemi/android/dfu/internal/ArchiveInputStream.java index fbb03fc..d9cb7f4 100644 --- a/dfu/src/main/java/no/nordicsemi/android/dfu/internal/ArchiveInputStream.java +++ b/dfu/src/main/java/no/nordicsemi/android/dfu/internal/ArchiveInputStream.java @@ -343,7 +343,7 @@ public class ArchiveInputStream extends InputStream { public void close() throws IOException { softDeviceBytes = null; bootloaderBytes = null; - softDeviceBytes = null; + applicationBytes = null; softDeviceAndBootloaderBytes = null; softDeviceSize = bootloaderSize = applicationSize = 0; currentSource = null; From e749cecf53e9282e02dd9956e6c799e175b13920 Mon Sep 17 00:00:00 2001 From: Aleksander Nowakowski Date: Wed, 20 Feb 2019 13:16:07 +0100 Subject: [PATCH 09/13] Version 1.9.0 --- README.md | 61 +++++++++++++++++------- build.gradle | 2 +- dfu/build.gradle | 8 ++-- gradle/wrapper/gradle-wrapper.properties | 4 +- 4 files changed, 52 insertions(+), 23 deletions(-) diff --git a/README.md b/README.md index 9d34b8c..7951381 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,14 @@ ### Usage -The compat library may be found on jcenter and Maven Central repository. Add it to your project by adding the following dependency: +The DFU library may be found on jcenter and Maven Central repository. Add it to your project by +adding the following dependency: + +```Groovy +implementation 'no.nordicsemi.android:dfu:1.9.0' +``` + +For projects not migrated to Android Jetpack, use: ```Groovy implementation 'no.nordicsemi.android:dfu:1.8.1' @@ -13,18 +20,28 @@ implementation 'no.nordicsemi.android:dfu:1.8.1' If you use proguard, add the following line to your proguard rules: ```-keep class no.nordicsemi.android.dfu.** { *; }``` +Starting from version 1.9.0 the library is able to retry a DFU update in case of an unwanted +disconnection. However, to maintain backward compatibility, this feature is by default disabled. +Call `initiator.setNumberOfRetries(int)` to set how many attempts the service should perform. +Secure DFU will be resumed after it has been interrupted from the point it stopped, while the +Legacy DFU will start again. + ### Device Firmware Update (DFU) -The nRF5x Series chips are flash-based SoCs, and as such they represent the most flexible solution available. A key feature of the nRF5x Series and their associated software architecture -and S-Series SoftDevices is the possibility for Over-The-Air Device Firmware Upgrade (OTA-DFU). See Figure 1. OTA-DFU allows firmware upgrades to be issued and downloaded to products -in the field via the cloud and so enables OEMs to fix bugs and introduce new features to products that are already out on the market. +The nRF5x Series chips are flash-based SoCs, and as such they represent the most flexible solution available. +A key feature of the nRF5x Series and their associated software architecture and S-Series SoftDevices +is the possibility for Over-The-Air Device Firmware Upgrade (OTA-DFU). See Figure 1. +OTA-DFU allows firmware upgrades to be issued and downloaded to products in the field via the cloud +and so enables OEMs to fix bugs and introduce new features to products that are already out on the market. This brings added security and flexibility to product development when using the nRF5x Series SoCs. ![Device Firmware Update](resources/dfu.png) -This repository contains a tested library for Android 4.3+ platform which may be used to perform Device Firmware Update on the nRF5x device using a phone or a tablet. +This repository contains a tested library for Android 4.3+ platform which may be used to perform +Device Firmware Update on the nRF5x device using a phone or a tablet. -DFU library has been designed to make it very easy to include these devices into your application. It is compatible with all Bootloader/DFU versions. +DFU library has been designed to make it very easy to include these devices into your application. +It is compatible with all Bootloader/DFU versions. [![Alt text for your video](http://img.youtube.com/vi/LdY2m_bZTgE/0.jpg)](http://youtu.be/LdY2m_bZTgE) @@ -34,38 +51,50 @@ See the [documentation](documentation) for more information. ### Requirements -The library is compatible with nRF51 and nRF52 devices with S-Series Soft Device and the DFU Bootloader flashed on. +The library is compatible with nRF51 and nRF52 devices with S-Series Soft Device and the +DFU Bootloader flashed on. ### DFU History #### Legacy DFU * **SDK 4.3.0** - First version of DFU over Bluetooth Smart. DFU supports Application update. -* **SDK 6.1.0** - DFU Bootloader supports Soft Device and Bootloader update. As the updated Bootloader may be dependent on the new Soft Device, those two may be sent and installed together. +* **SDK 6.1.0** - DFU Bootloader supports Soft Device and Bootloader update. As the updated + Bootloader may be dependent on the new Soft Device, those two may be sent and + installed together. - Buttonless update support for non-bonded devices. -* **SDK 7.0.0** - The extended init packet is required. The init packet contains additional validation information: device type and revision, application version, compatible Soft Devices and the firmware CRC. -* **SDK 8.0.0** - The bond information may be preserved after an application update. The new application, when first started, will send the Service Change indication to the phone to refresh the services. +* **SDK 7.0.0** - The extended init packet is required. The init packet contains additional + validation information: device type and revision, application version, compatible + Soft Devices and the firmware CRC. +* **SDK 8.0.0** - The bond information may be preserved after an application update. + The new application, when first started, will send the Service Change indication + to the phone to refresh the services. - Buttonless update support for bonded devices - sharing the LTK between an app and the bootloader. #### Secure DFU * **SDK 12.0.0** - New Secure DFU has been released. Buttonless service is experimental. -* **SDK 13.0.0** - Buttonless DFU (still experimental) uses different UUIDs. No bond sharing supported. Bootloader will use address +1. -* **SDK 14.0.0** - Buttonless DFU is no longer experimental. A new UUID (0004) added for bonded only devices (previous one (0003) is for non-bonded only). +* **SDK 13.0.0** - Buttonless DFU (still experimental) uses different UUIDs. No bond sharing + supported. Bootloader will use address +1. +* **SDK 14.0.0** - Buttonless DFU is no longer experimental. A new UUID (0004) added for bonded + only devices (previous one (0003) is for non-bonded only). * **SDK 15.0.0** - Support for higher MTUs added. This library is fully backwards compatible and supports both the new and legacy DFU. -The experimental buttonless DFU service from SDK 12 is supported since version 1.1.0. Due to the fact, that this experimental service from SDK 12 is not safe, -you have to call [starter.setUnsafeExperimentalButtonlessServiceInSecureDfuEnabled(true)](https://github.com/NordicSemiconductor/Android-DFU-Library/blob/release/dfu/src/main/java/no/nordicsemi/android/dfu/DfuServiceInitiator.java#L376) -to enable it. Read the method documentation for details. It is recommended to use the Buttonless service from SDK 13 (for non-bonded devices, or 14 for bonded). +The experimental buttonless DFU service from SDK 12 is supported since version 1.1.0. +Due to the fact, that this experimental service from SDK 12 is not safe, you have to call +[starter.setUnsafeExperimentalButtonlessServiceInSecureDfuEnabled(true)](https://github.com/NordicSemiconductor/Android-DFU-Library/blob/release/dfu/src/main/java/no/nordicsemi/android/dfu/DfuServiceInitiator.java#L376) +to enable it. Read the method documentation for details. It is recommended to use the Buttonless +service from SDK 13 (for non-bonded devices, or 14 for bonded). Both are supported since DFU Library 1.3.0. Check platform folders for mode details about compatibility for each library. ### React Native -A library for both iOS and Android that is based on this library is available for React Native: [react-native-nordic-dfu](https://github.com/Pilloxa/react-native-nordic-dfu) +A library for both iOS and Android that is based on this library is available for React Native: +[react-native-nordic-dfu](https://github.com/Pilloxa/react-native-nordic-dfu) ### Resources diff --git a/build.gradle b/build.gradle index ca95869..c4150c2 100644 --- a/build.gradle +++ b/build.gradle @@ -6,7 +6,7 @@ buildscript { google() } dependencies { - classpath 'com.android.tools.build:gradle:3.2.1' + classpath 'com.android.tools.build:gradle:3.3.1' classpath "com.jfrog.bintray.gradle:gradle-bintray-plugin:1.8.4" classpath 'com.github.dcendents:android-maven-gradle-plugin:2.1' diff --git a/dfu/build.gradle b/dfu/build.gradle index 99c6fd5..a6be985 100644 --- a/dfu/build.gradle +++ b/dfu/build.gradle @@ -15,7 +15,7 @@ apply plugin: 'com.jfrog.bintray' ext { PUBLISH_GROUP_ID = 'no.nordicsemi.android' PUBLISH_ARTIFACT_ID = 'dfu' - PUBLISH_VERSION = '1.8.1' + PUBLISH_VERSION = '1.9.0' bintrayRepo = 'android' bintrayName = 'dfu-library' @@ -45,8 +45,8 @@ android { defaultConfig { minSdkVersion 18 targetSdkVersion 28 - versionCode 22 - versionName "1.8.1" + versionCode 23 + versionName "1.9.0" } buildTypes { @@ -58,7 +58,7 @@ android { } dependencies { - implementation 'androidx.core:core:1.1.0-alpha02' + implementation 'androidx.core:core:1.1.0-alpha04' implementation 'androidx.localbroadcastmanager:localbroadcastmanager:1.0.0' implementation 'androidx.annotation:annotation:1.0.1' implementation 'com.google.code.gson:gson:2.8.5' diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index ab28c60..10e7332 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Mon Oct 01 13:36:04 CEST 2018 +#Wed Feb 20 13:14:30 CET 2019 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.2-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.1-all.zip From a24f1e8cd91787d690b47cb8a4fa35d3383d55ef Mon Sep 17 00:00:00 2001 From: Aleksander Nowakowski Date: Wed, 20 Feb 2019 14:40:49 +0100 Subject: [PATCH 10/13] Reporting disconnection before an error --- .../android/dfu/BaseCustomDfuImpl.java | 8 ++++---- .../no/nordicsemi/android/dfu/BaseDfuImpl.java | 16 ++++++++-------- .../android/dfu/LegacyButtonlessDfuImpl.java | 4 ++-- .../no/nordicsemi/android/dfu/LegacyDfuImpl.java | 8 ++++---- 4 files changed, 18 insertions(+), 18 deletions(-) diff --git a/dfu/src/main/java/no/nordicsemi/android/dfu/BaseCustomDfuImpl.java b/dfu/src/main/java/no/nordicsemi/android/dfu/BaseCustomDfuImpl.java index dabe2f7..d673435 100644 --- a/dfu/src/main/java/no/nordicsemi/android/dfu/BaseCustomDfuImpl.java +++ b/dfu/src/main/java/no/nordicsemi/android/dfu/BaseCustomDfuImpl.java @@ -347,10 +347,10 @@ import no.nordicsemi.android.dfu.internal.exception.UploadAbortedException; } catch (final InterruptedException e) { loge("Sleeping interrupted", e); } - if (mError != 0) - throw new DfuException("Unable to write Init DFU Parameters", mError); if (!mConnected) throw new DeviceDisconnectedException("Unable to write Init DFU Parameters: device disconnected"); + if (mError != 0) + throw new DfuException("Unable to write Init DFU Parameters", mError); } /** @@ -394,10 +394,10 @@ import no.nordicsemi.android.dfu.internal.exception.UploadAbortedException; loge("Sleeping interrupted", e); } - if (mError != 0) - throw new DfuException("Uploading Firmware Image failed", mError); if (!mConnected) throw new DeviceDisconnectedException("Uploading Firmware Image failed: device disconnected"); + if (mError != 0) + throw new DfuException("Uploading Firmware Image failed", mError); } /** diff --git a/dfu/src/main/java/no/nordicsemi/android/dfu/BaseDfuImpl.java b/dfu/src/main/java/no/nordicsemi/android/dfu/BaseDfuImpl.java index 9d9ef96..70ea361 100644 --- a/dfu/src/main/java/no/nordicsemi/android/dfu/BaseDfuImpl.java +++ b/dfu/src/main/java/no/nordicsemi/android/dfu/BaseDfuImpl.java @@ -453,10 +453,10 @@ import no.nordicsemi.android.dfu.internal.scanner.BootloaderScannerFactory; } catch (final InterruptedException e) { loge("Sleeping interrupted", e); } - if (mError != 0) - throw new DfuException("Unable to set " + debugString + " state", mError); if (!mConnected) throw new DeviceDisconnectedException("Unable to set " + debugString + " state: device disconnected"); + if (mError != 0) + throw new DfuException("Unable to set " + debugString + " state", mError); } /** @@ -506,10 +506,10 @@ import no.nordicsemi.android.dfu.internal.scanner.BootloaderScannerFactory; } catch (final InterruptedException e) { loge("Sleeping interrupted", e); } - if (mError != 0) - throw new DfuException("Unable to read Service Changed CCCD", mError); if (!mConnected) throw new DeviceDisconnectedException("Unable to read Service Changed CCCD: device disconnected"); + if (mError != 0) + throw new DfuException("Unable to read Service Changed CCCD", mError); // Return true if the CCCD value is return descriptor.getValue() != null && descriptor.getValue().length == 2 @@ -562,10 +562,10 @@ import no.nordicsemi.android.dfu.internal.scanner.BootloaderScannerFactory; } catch (final InterruptedException e) { loge("Sleeping interrupted", e); } - if (!mResetRequestSent && mError != 0) - throw new DfuException("Unable to write Op Code " + value[0], mError); if (!mResetRequestSent && !mConnected) throw new DeviceDisconnectedException("Unable to write Op Code " + value[0] + ": device disconnected"); + if (!mResetRequestSent && mError != 0) + throw new DfuException("Unable to write Op Code " + value[0], mError); } /** @@ -725,10 +725,10 @@ import no.nordicsemi.android.dfu.internal.scanner.BootloaderScannerFactory; } if (mAborted) throw new UploadAbortedException(); - if (mError != 0) - throw new DfuException("Unable to write Op Code", mError); if (!mConnected) throw new DeviceDisconnectedException("Unable to write Op Code: device disconnected"); + if (mError != 0) + throw new DfuException("Unable to write Op Code", mError); return mReceivedData; } diff --git a/dfu/src/main/java/no/nordicsemi/android/dfu/LegacyButtonlessDfuImpl.java b/dfu/src/main/java/no/nordicsemi/android/dfu/LegacyButtonlessDfuImpl.java index 85b84dd..d5fed92 100644 --- a/dfu/src/main/java/no/nordicsemi/android/dfu/LegacyButtonlessDfuImpl.java +++ b/dfu/src/main/java/no/nordicsemi/android/dfu/LegacyButtonlessDfuImpl.java @@ -238,10 +238,10 @@ import no.nordicsemi.android.dfu.internal.exception.UploadAbortedException; } catch (final InterruptedException e) { loge("Sleeping interrupted", e); } - if (mError != 0) - throw new DfuException("Unable to read version number", mError); if (!mConnected) throw new DeviceDisconnectedException("Unable to read version number: device disconnected"); + if (mError != 0) + throw new DfuException("Unable to read version number", mError); // The version is a 16-bit unsigned int return characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT16, 0); diff --git a/dfu/src/main/java/no/nordicsemi/android/dfu/LegacyDfuImpl.java b/dfu/src/main/java/no/nordicsemi/android/dfu/LegacyDfuImpl.java index 9405d46..911c872 100644 --- a/dfu/src/main/java/no/nordicsemi/android/dfu/LegacyDfuImpl.java +++ b/dfu/src/main/java/no/nordicsemi/android/dfu/LegacyDfuImpl.java @@ -647,10 +647,10 @@ import no.nordicsemi.android.error.LegacyDfuError; } if (mAborted) throw new UploadAbortedException(); - if (mError != 0) - throw new DfuException("Unable to write Image Size", mError); if (!mConnected) throw new DeviceDisconnectedException("Unable to write Image Size: device disconnected"); + if (mError != 0) + throw new DfuException("Unable to write Image Size", mError); } /** @@ -701,10 +701,10 @@ import no.nordicsemi.android.error.LegacyDfuError; } if (mAborted) throw new UploadAbortedException(); - if (mError != 0) - throw new DfuException("Unable to write Image Sizes", mError); if (!mConnected) throw new DeviceDisconnectedException("Unable to write Image Sizes: device disconnected"); + if (mError != 0) + throw new DfuException("Unable to write Image Sizes", mError); } /** From 5296cc531423f3a21b2c5469137872e6c324d694 Mon Sep 17 00:00:00 2001 From: Aleksander Nowakowski Date: Thu, 21 Feb 2019 09:48:02 +0100 Subject: [PATCH 11/13] Comments regarding Buttonless service --- .../no/nordicsemi/android/dfu/DfuServiceInitiator.java | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/dfu/src/main/java/no/nordicsemi/android/dfu/DfuServiceInitiator.java b/dfu/src/main/java/no/nordicsemi/android/dfu/DfuServiceInitiator.java index b5438a4..dde34bb 100644 --- a/dfu/src/main/java/no/nordicsemi/android/dfu/DfuServiceInitiator.java +++ b/dfu/src/main/java/no/nordicsemi/android/dfu/DfuServiceInitiator.java @@ -401,9 +401,11 @@ public class DfuServiceInitiator { * 0x01 - Success
      * The device should disconnect and restart in DFU mode after sending the notification. *

      - * In SDK 13 this issue will be fixed by a proper implementation (bonding required, - * passing bond information to the bootloader, encryption, well tested). It is recommended - * to use this new service when SDK 13 (or later) is out. TODO fix the docs when SDK 13 is out. + * The Buttonless service has changed in SDK 13 and later. Indications are used instead of + * notifications. Also, Buttonless service for bonded devices has been added. + * It is recommended to use any of the new services instead. + * + * @return the builder */ public DfuServiceInitiator setUnsafeExperimentalButtonlessServiceInSecureDfuEnabled(final boolean enable) { this.enableUnsafeExperimentalButtonlessDfu = enable; From ebe65dbd648c7f85dfd2176740ae43bd6969b576 Mon Sep 17 00:00:00 2001 From: Aleksander Nowakowski Date: Thu, 21 Feb 2019 09:54:10 +0100 Subject: [PATCH 12/13] Last warnings removed --- .../main/java/no/nordicsemi/android/dfu/BaseCustomDfuImpl.java | 1 + dfu/src/main/java/no/nordicsemi/android/dfu/FileType.java | 3 +-- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/dfu/src/main/java/no/nordicsemi/android/dfu/BaseCustomDfuImpl.java b/dfu/src/main/java/no/nordicsemi/android/dfu/BaseCustomDfuImpl.java index d673435..99eae3f 100644 --- a/dfu/src/main/java/no/nordicsemi/android/dfu/BaseCustomDfuImpl.java +++ b/dfu/src/main/java/no/nordicsemi/android/dfu/BaseCustomDfuImpl.java @@ -273,6 +273,7 @@ import no.nordicsemi.android.dfu.internal.exception.UploadAbortedException; } } + @SuppressWarnings("unused") protected abstract UUID getControlPointCharacteristicUUID(); protected abstract UUID getPacketCharacteristicUUID(); diff --git a/dfu/src/main/java/no/nordicsemi/android/dfu/FileType.java b/dfu/src/main/java/no/nordicsemi/android/dfu/FileType.java index e80ed54..4949a98 100644 --- a/dfu/src/main/java/no/nordicsemi/android/dfu/FileType.java +++ b/dfu/src/main/java/no/nordicsemi/android/dfu/FileType.java @@ -9,6 +9,5 @@ import androidx.annotation.IntDef; DfuBaseService.TYPE_APPLICATION }, flag = true) -public @interface FileType { -} +public @interface FileType {} From 1a40f78a08ecdc58506e30ddb42a3a6e61487b06 Mon Sep 17 00:00:00 2001 From: Aleksander Nowakowski Date: Thu, 21 Feb 2019 10:57:29 +0100 Subject: [PATCH 13/13] Deprecating last settings constant --- .../android/dfu/DfuBaseService.java | 25 ++++++++++---- .../android/dfu/DfuServiceInitiator.java | 33 +++++++++++++++++-- .../android/dfu/DfuSettingsConstants.java | 16 +++++---- 3 files changed, 60 insertions(+), 14 deletions(-) diff --git a/dfu/src/main/java/no/nordicsemi/android/dfu/DfuBaseService.java b/dfu/src/main/java/no/nordicsemi/android/dfu/DfuBaseService.java index d3982fc..6abe3ea 100644 --- a/dfu/src/main/java/no/nordicsemi/android/dfu/DfuBaseService.java +++ b/dfu/src/main/java/no/nordicsemi/android/dfu/DfuBaseService.java @@ -93,6 +93,7 @@ import no.nordicsemi.android.error.GattError; * The service will show its progress on the notification bar and will send local broadcasts to the * application. */ +@SuppressWarnings("deprecation") public abstract class DfuBaseService extends IntentService implements DfuProgressInfo.ProgressListener { private static final String TAG = "DfuBaseService"; @@ -214,6 +215,12 @@ public abstract class DfuBaseService extends IntentService implements DfuProgres * #71. */ public static final String EXTRA_DISABLE_RESUME = "no.nordicsemi.android.dfu.extra.EXTRA_DISABLE_RESUME"; + /** + * The MBR size. + * + * @see DfuServiceInitiator#setMbrSize(int) + */ + public static final String EXTRA_MBR_SIZE = "no.nordicsemi.android.dfu.extra.EXTRA_MBR_SIZE"; /** * This extra allows you to control the MTU that will be requested (on Lollipop or newer devices). * If the field is null, the service will not request higher MTU and will use MTU = 23 @@ -1108,14 +1115,20 @@ public abstract class DfuBaseService extends IntentService implements DfuProgres // Applications and bootloader starts from bigger address. However, in custom DFU // implementations, user may want to transmit the whole whole data, even from address 0x0000. final SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this); - final String value = preferences.getString(DfuSettingsConstants.SETTINGS_MBR_SIZE, String.valueOf(DfuSettingsConstants.SETTINGS_DEFAULT_MBR_SIZE)); - int mbrSize; - try { - mbrSize = Integer.parseInt(value); + int mbrSize = DfuServiceInitiator.DEFAULT_MBR_SIZE; + if (preferences.contains(DfuSettingsConstants.SETTINGS_MBR_SIZE)) { + final String value = preferences.getString(DfuSettingsConstants.SETTINGS_MBR_SIZE, String.valueOf(DfuServiceInitiator.DEFAULT_MBR_SIZE)); + try { + mbrSize = Integer.parseInt(value); + if (mbrSize < 0) + mbrSize = 0; + } catch (final NumberFormatException e) { + // ignore, default value will be used + } + } else { + mbrSize = intent.getIntExtra(EXTRA_MBR_SIZE, DfuServiceInitiator.DEFAULT_MBR_SIZE); if (mbrSize < 0) mbrSize = 0; - } catch (final NumberFormatException e) { - mbrSize = DfuSettingsConstants.SETTINGS_DEFAULT_MBR_SIZE; } if (foregroundService) { diff --git a/dfu/src/main/java/no/nordicsemi/android/dfu/DfuServiceInitiator.java b/dfu/src/main/java/no/nordicsemi/android/dfu/DfuServiceInitiator.java index dde34bb..5b4ea76 100644 --- a/dfu/src/main/java/no/nordicsemi/android/dfu/DfuServiceInitiator.java +++ b/dfu/src/main/java/no/nordicsemi/android/dfu/DfuServiceInitiator.java @@ -48,9 +48,10 @@ import java.util.UUID; * parameters to the service. The DfuServiceInitiator class may be used to make this process easier. * It provides simple API that covers all low lever operations. */ -@SuppressWarnings({"WeakerAccess", "unused"}) -public class DfuServiceInitiator { +@SuppressWarnings({"WeakerAccess", "unused", "deprecation"}) +public final class DfuServiceInitiator { public static final int DEFAULT_PRN_VALUE = 12; + public static final int DEFAULT_MBR_SIZE = 0x1000; /** Constant used to narrow the scope of the update to system components (SD+BL) only. */ public static final int SCOPE_SYSTEM_COMPONENTS = 1; @@ -80,6 +81,7 @@ public class DfuServiceInitiator { private boolean enableUnsafeExperimentalButtonlessDfu = false; private boolean disableResume = false; private int numberOfRetries = 0; // 0 to be backwards compatible + private int mbrSize = DEFAULT_MBR_SIZE; private Boolean packetReceiptNotificationsEnabled; private int numberOfPackets = 12; @@ -374,6 +376,32 @@ public class DfuServiceInitiator { return this; } + /** + * This method sets the size of an MBR (Master Boot Record). It should be used only + * when updating a file from a HEX file. If you use BIN or ZIP, value set here will + * be ignored. + *

      + * The MBR size is important for the HEX parser, which has to cut it from the Soft Device's + * HEX before sending it to the DFU target. The MBR can't be updated using DFU, and the + * bootloader expects only the Soft Device bytes. Usually, the Soft Device HEX provided + * by Nordic contains an MBR at addresses 0x0000 to 0x1000. + * 0x1000 is the default size of MBR which will be used. + *

      + * If you have a HEX file which address start from 0 and want to send the whole BIN content + * from it, you have to set the MBR size to 0, otherwise first 4096 bytes will be cut off. + *

      + * The value set here will not be used if the {@link DfuSettingsConstants#SETTINGS_MBR_SIZE} + * is set in Shared Preferences. + * + * @param mbrSize the MBR size in bytes. Defaults to 4096 (0x1000) bytes. + * @return the builder + * @see DfuSettingsConstants#SETTINGS_MBR_SIZE + */ + public DfuServiceInitiator setMbrSize(@IntRange(from = 0) final int mbrSize) { + this.mbrSize = mbrSize; + return this; + } + /** * Set this flag to true to enable experimental buttonless feature in Secure DFU. When the * experimental Buttonless DFU Service is found on a device, the service will use it to @@ -730,6 +758,7 @@ public class DfuServiceInitiator { intent.putExtra(DfuBaseService.EXTRA_FORCE_DFU, forceDfu); intent.putExtra(DfuBaseService.EXTRA_DISABLE_RESUME, disableResume); intent.putExtra(DfuBaseService.EXTRA_MAX_DFU_ATTEMPTS, numberOfRetries); + intent.putExtra(DfuBaseService.EXTRA_MBR_SIZE, mbrSize); if (mtu > 0) intent.putExtra(DfuBaseService.EXTRA_MTU, mtu); intent.putExtra(DfuBaseService.EXTRA_CURRENT_MTU, currentMtu); diff --git a/dfu/src/main/java/no/nordicsemi/android/dfu/DfuSettingsConstants.java b/dfu/src/main/java/no/nordicsemi/android/dfu/DfuSettingsConstants.java index 747e49a..fc9e4a0 100644 --- a/dfu/src/main/java/no/nordicsemi/android/dfu/DfuSettingsConstants.java +++ b/dfu/src/main/java/no/nordicsemi/android/dfu/DfuSettingsConstants.java @@ -24,7 +24,14 @@ package no.nordicsemi.android.dfu; import android.bluetooth.BluetoothGattCharacteristic; +/** + * A collection of constants used for reading DFU constants from + * {@link android.preference.PreferenceManager} in previous versions of DFU Library. + * + * @deprecated Use {@link DfuServiceInitiator} methods instead. + */ @SuppressWarnings("DeprecatedIsStillUsed") +@Deprecated public interface DfuSettingsConstants { /** * This property must contain a boolean value. @@ -91,15 +98,12 @@ public interface DfuSettingsConstants { * If you are using the PC nrf util tool to create a ZIP Distribution Packet with the * firmware and Init Packet this option does not apply as the nrf tool will convert * HEX to BIN itself. + * + * @deprecated Use {@link DfuServiceInitiator#setMbrSize(int)} instead. */ + @Deprecated String SETTINGS_MBR_SIZE = "settings_mbr_size"; - /** - * The default value of the MBR size. - * @see #SETTINGS_DEFAULT_MBR_SIZE - */ - int SETTINGS_DEFAULT_MBR_SIZE = 0x1000; - /** * This property must contain a boolean value. *