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.  -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. [](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/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..99eae3f 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; @@ -254,6 +273,7 @@ import no.nordicsemi.android.dfu.internal.exception.UploadAbortedException; } } + @SuppressWarnings("unused") protected abstract UUID getControlPointCharacteristicUUID(); protected abstract UUID getPacketCharacteristicUUID(); @@ -262,13 +282,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 +307,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; @@ -323,23 +348,25 @@ 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); } /** - * 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 +377,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); @@ -367,18 +395,19 @@ 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); } /** - * 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 +420,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 +457,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 +472,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 +484,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 9d86e13..70ea361 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 @@ -417,21 +453,23 @@ 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); } /** * Reads the value of the Service Changed Client Characteristic Configuration descriptor (CCCD). * - * @returntrue
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)
@@ -468,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
@@ -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;
@@ -519,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);
}
/**
@@ -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) {
@@ -679,20 +725,21 @@ 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;
}
/**
* 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...");
@@ -707,10 +754,14 @@ 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);
}
- 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 824cd61..6abe3ea 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; @@ -90,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"; @@ -107,60 +111,96 @@ 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";
- /** 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";
+ /**
+ * 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. - *
+ * 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 totrue
.
+ *
+ * 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"; /** @@ -175,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 @@ -195,13 +241,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
@@ -214,36 +261,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"; /** @@ -255,26 +319,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"; /** @@ -283,27 +354,32 @@ public abstract class DfuBaseService extends IntentService implements DfuProgres *
null
or is equal to {@value #MIME_TYPE_OCTET_STREAM} - the {@link #TYPE_APPLICATION} is assumed.null
or is equal to
+ * {@value #MIME_TYPE_OCTET_STREAM} - the {@link #TYPE_APPLICATION} is assumed.* 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 */ @@ -311,11 +387,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 */ @@ -323,31 +398,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 */ @@ -357,7 +438,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: *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;
@@ -1372,12 +1510,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);
@@ -1392,12 +1531,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;
@@ -1415,7 +1555,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 {
@@ -1430,7 +1571,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) {
@@ -1446,7 +1588,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...");
@@ -1456,10 +1598,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) {
/*
@@ -1474,11 +1617,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");
@@ -1487,7 +1629,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() {
@@ -1576,9 +1719,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);
@@ -1589,9 +1733,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);
@@ -1633,7 +1778,8 @@ public abstract class DfuBaseService extends IntentService implements DfuProgres
* This method allows you to update the notification showing an error.
* @param builder error notification builder
*/
- protected void updateErrorNotification(final NotificationCompat.Builder builder) {
+ @SuppressWarnings("unused")
+ protected void updateErrorNotification(@NonNull final NotificationCompat.Builder builder) {
// Empty default implementation
}
@@ -1666,10 +1812,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
*/
- protected void updateForegroundNotification(final NotificationCompat.Builder builder) {
+ @SuppressWarnings("unused")
+ protected void updateForegroundNotification(@NonNull final NotificationCompat.Builder builder) {
// Empty default implementation
}
@@ -1685,15 +1834,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
.
- *
true
if initialization was successful
+ * @return True
if initialization was successful.
*/
+ @SuppressWarnings("UnusedReturnValue")
private boolean initialize() {
// For API level 18 and above, get a reference to BluetoothAdapter through
// BluetoothManager.
@@ -1790,7 +1940,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) {
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:
* 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/DfuServiceInitiator.java b/dfu/src/main/java/no/nordicsemi/android/dfu/DfuServiceInitiator.java index b455529..5b4ea76 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; @@ -45,14 +48,15 @@ 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 = 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; @@ -76,6 +80,8 @@ 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 int mbrSize = DEFAULT_MBR_SIZE; private Boolean packetReceiptNotificationsEnabled; private int numberOfPackets = 12; @@ -195,13 +201,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; } @@ -262,6 +272,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 @@ -282,7 +314,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 +336,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,17 +363,45 @@ 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; } + /** + * 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
@@ -369,9 +429,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; @@ -526,7 +588,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 +620,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 +636,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 +654,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 +670,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 +711,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); } @@ -695,6 +757,8 @@ 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); + intent.putExtra(DfuBaseService.EXTRA_MBR_SIZE, mbrSize); if (mtu > 0) intent.putExtra(DfuBaseService.EXTRA_MTU, mtu); intent.putExtra(DfuBaseService.EXTRA_CURRENT_MTU, currentMtu); @@ -732,7 +796,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 +808,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/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..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,6 +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. @@ -90,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. *
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 new file mode 100644 index 0000000..4949a98 --- /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; + +@SuppressWarnings("WeakerAccess") +@IntDef(value = { + DfuBaseService.TYPE_SOFT_DEVICE, + DfuBaseService.TYPE_BOOTLOADER, + DfuBaseService.TYPE_APPLICATION + }, + flag = true) +public @interface FileType {} + 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..d5fed92 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(); } @@ -233,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 a675c69..911c872 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); @@ -474,7 +477,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(); @@ -550,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); } @@ -562,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]; } @@ -575,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; } @@ -589,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; @@ -634,31 +647,35 @@ 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); } /** *
- * 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; @@ -684,21 +701,24 @@ 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); } /** * 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 87b2752..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: *+ * 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"); @@ -731,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"); @@ -768,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"); @@ -798,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"); @@ -828,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"); @@ -861,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) { @@ -890,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..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 @@ -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
- * 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:
*
+ * 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)");
}
}
@@ -327,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;
@@ -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
*
*
- * @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).
+ * 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;
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