Merge pull request #170 from NordicSemiconductor/feature/retries
New feature: retrying DFU in case of undesired disconnection
This commit is contained in:
commit
bbecebe9a1
61
README.md
61
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
|
||||
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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;
|
||||
/**
|
||||
* <p>
|
||||
* Flag set to <code>true</code> when the DFU target had send a notification with status other than success. Setting it to <code>true</code> will abort sending firmware and
|
||||
* Flag set to <code>true</code> when the DFU target had send a notification with status other
|
||||
* than success. Setting it to <code>true</code> will abort sending firmware and
|
||||
* stop logging notifications (read below for explanation).
|
||||
* </p>
|
||||
* <p>
|
||||
* 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.
|
||||
* </p>
|
||||
* 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.
|
||||
* <p>
|
||||
* 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.
|
||||
* </p>
|
||||
* 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.
|
||||
* <p>
|
||||
* N* - Value of Packet Receipt Notification, 12 by default.
|
||||
* </p>
|
||||
*/
|
||||
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.
|
||||
*/
|
||||
|
||||
/*
|
||||
|
|
|
@ -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).
|
||||
*
|
||||
* @return <code>true</code> if Service Changed CCCD is enabled and set to INDICATE
|
||||
* @throws DeviceDisconnectedException
|
||||
* @throws DfuException
|
||||
* @throws UploadAbortedException
|
||||
* @return <code>true</code> 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 <code>true</code> if operation succeeded, <code>false</code> otherwise
|
||||
* @return <code>true</code> if operation succeeded, <code>false</code> 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 "";
|
||||
|
||||
|
|
|
@ -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];
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
* <p>
|
||||
* 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");
|
||||
|
|
|
@ -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.
|
||||
* <p>
|
||||
* An application that supports one of those services should have the Secure DFU Service with one
|
||||
* of those characteristic inside.
|
||||
* <p>
|
||||
* 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.
|
||||
* <p>
|
||||
* 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)");
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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:
|
||||
* <ul>
|
||||
|
|
|
@ -54,12 +54,13 @@ import androidx.annotation.NonNull;
|
|||
return this;
|
||||
}
|
||||
|
||||
@SuppressWarnings({"UnusedReturnValue", "SameParameterValue"})
|
||||
DfuProgressInfo setTotalPart(final int totalParts) {
|
||||
this.totalParts = totalParts;
|
||||
return this;
|
||||
}
|
||||
|
||||
public void setProgress(final int progress) {
|
||||
void setProgress(final int progress) {
|
||||
this.progress = progress;
|
||||
mListener.updateProgressNotification();
|
||||
}
|
||||
|
@ -108,10 +109,12 @@ import androidx.annotation.NonNull;
|
|||
return bytesSent;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
int getBytesReceived() {
|
||||
return bytesReceived;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
int getImageSizeInBytes() {
|
||||
return imageSizeInBytes;
|
||||
}
|
||||
|
|
|
@ -22,92 +22,120 @@
|
|||
|
||||
package no.nordicsemi.android.dfu;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
/**
|
||||
* Listener for status, progress and error events. This listener should be used instead of creating the BroadcastReceiver on your own.
|
||||
* Listener for status, progress and error events. This listener should be used instead of
|
||||
* creating the BroadcastReceiver on your own.
|
||||
*
|
||||
* @see DfuServiceListenerHelper
|
||||
*/
|
||||
public interface DfuProgressListener {
|
||||
|
||||
/**
|
||||
* Method called when the DFU service started connecting with the DFU target.
|
||||
* @param deviceAddress the target device address
|
||||
*
|
||||
* @param deviceAddress the target device address.
|
||||
*/
|
||||
void onDeviceConnecting(final String deviceAddress);
|
||||
void onDeviceConnecting(@NonNull final String deviceAddress);
|
||||
|
||||
/**
|
||||
* Method called when the service has successfully connected, discovered services and found DFU service on the DFU target.
|
||||
* @param deviceAddress the target device address
|
||||
* Method called when the service has successfully connected, discovered services and found
|
||||
* DFU service on the DFU target.
|
||||
*
|
||||
* @param deviceAddress the target device address.
|
||||
*/
|
||||
void onDeviceConnected(final String deviceAddress);
|
||||
void onDeviceConnected(@NonNull final String deviceAddress);
|
||||
|
||||
/**
|
||||
* Method called when the DFU process is starting. This includes reading the DFU Version characteristic, sending DFU_START command as well as the Init packet, if set.
|
||||
* @param deviceAddress the target device address
|
||||
* Method called when the DFU process is starting. This includes reading the DFU Version
|
||||
* characteristic, sending DFU_START command as well as the Init packet, if set.
|
||||
*
|
||||
* @param deviceAddress the target device address.
|
||||
*/
|
||||
void onDfuProcessStarting(final String deviceAddress);
|
||||
void onDfuProcessStarting(@NonNull final String deviceAddress);
|
||||
|
||||
/**
|
||||
* Method called when the DFU process was started and bytes about to be sent.
|
||||
*
|
||||
* @param deviceAddress the target device address
|
||||
*/
|
||||
void onDfuProcessStarted(final String deviceAddress);
|
||||
void onDfuProcessStarted(@NonNull final String deviceAddress);
|
||||
|
||||
/**
|
||||
* Method called when the service discovered that the DFU target is in the application mode and must be switched to DFU mode.
|
||||
* The switch command will be sent and the DFU process should start again. There will be no {@link #onDeviceDisconnected(String)} event following this call.
|
||||
* @param deviceAddress the target device address
|
||||
* Method called when the service discovered that the DFU target is in the application mode
|
||||
* and must be switched to DFU mode. The switch command will be sent and the DFU process
|
||||
* should start again. There will be no {@link #onDeviceDisconnected(String)} event following
|
||||
* this call.
|
||||
*
|
||||
* @param deviceAddress the target device address.
|
||||
*/
|
||||
void onEnablingDfuMode(final String deviceAddress);
|
||||
void onEnablingDfuMode(@NonNull final String deviceAddress);
|
||||
|
||||
/**
|
||||
* Method called during uploading the firmware. It will not be called twice with the same value of percent, however, in case of small firmware files, some values may be omitted.
|
||||
* @param deviceAddress the target device address
|
||||
* @param percent the current status of upload (0-99)
|
||||
* @param speed the current speed in bytes per millisecond
|
||||
* @param avgSpeed the average speed in bytes per millisecond
|
||||
* @param currentPart the number pf part being sent. In case the ZIP file contains a Soft Device and/or a Bootloader together with the application the SD+BL are sent as part 1,
|
||||
* then the service starts again and send the application as part 2
|
||||
* @param partsTotal total number of parts
|
||||
* Method called during uploading the firmware. It will not be called twice with the same
|
||||
* value of percent, however, in case of small firmware files, some values may be omitted.
|
||||
*
|
||||
* @param deviceAddress the target device address.
|
||||
* @param percent the current status of upload (0-99).
|
||||
* @param speed the current speed in bytes per millisecond.
|
||||
* @param avgSpeed the average speed in bytes per millisecond
|
||||
* @param currentPart the number pf part being sent. In case the ZIP file contains a Soft Device
|
||||
* and/or a Bootloader together with the application the SD+BL are sent as part 1,
|
||||
* then the service starts again and send the application as part 2.
|
||||
* @param partsTotal total number of parts.
|
||||
*/
|
||||
void onProgressChanged(final String deviceAddress, final int percent, final float speed, final float avgSpeed, final int currentPart, final int partsTotal);
|
||||
void onProgressChanged(@NonNull final String deviceAddress, final int percent,
|
||||
final float speed, final float avgSpeed,
|
||||
final int currentPart, final int partsTotal);
|
||||
|
||||
/**
|
||||
* Method called when the new firmware is being validated on the target device.
|
||||
* @param deviceAddress the target device address
|
||||
*
|
||||
* @param deviceAddress the target device address.
|
||||
*/
|
||||
void onFirmwareValidating(final String deviceAddress);
|
||||
void onFirmwareValidating(@NonNull final String deviceAddress);
|
||||
|
||||
/**
|
||||
* Method called when the service started to disconnect from the target device.
|
||||
* @param deviceAddress the target device address
|
||||
*
|
||||
* @param deviceAddress the target device address.
|
||||
*/
|
||||
void onDeviceDisconnecting(final String deviceAddress);
|
||||
|
||||
/**
|
||||
* Method called when the service disconnected from the device. The device has been reset.
|
||||
* @param deviceAddress the target device address
|
||||
*
|
||||
* @param deviceAddress the target device address.
|
||||
*/
|
||||
void onDeviceDisconnected(final String deviceAddress);
|
||||
void onDeviceDisconnected(@NonNull final String deviceAddress);
|
||||
|
||||
/**
|
||||
* Method called when the DFU process succeeded.
|
||||
* @param deviceAddress the target device address
|
||||
*
|
||||
* @param deviceAddress the target device address.
|
||||
*/
|
||||
void onDfuCompleted(final String deviceAddress);
|
||||
void onDfuCompleted(@NonNull final String deviceAddress);
|
||||
|
||||
/**
|
||||
* Method called when the DFU process has been aborted.
|
||||
* @param deviceAddress the target device address
|
||||
*
|
||||
* @param deviceAddress the target device address.
|
||||
*/
|
||||
void onDfuAborted(final String deviceAddress);
|
||||
void onDfuAborted(@NonNull final String deviceAddress);
|
||||
|
||||
/**
|
||||
* Method called when an error occur.
|
||||
* @param deviceAddress the target device address
|
||||
* @param error error number
|
||||
* @param errorType the error type, one of {@link DfuBaseService#ERROR_TYPE_COMMUNICATION_STATE}, {@link DfuBaseService#ERROR_TYPE_COMMUNICATION},
|
||||
* {@link DfuBaseService#ERROR_TYPE_DFU_REMOTE}, {@link DfuBaseService#ERROR_TYPE_OTHER}.
|
||||
* @param message the error message
|
||||
*
|
||||
* @param deviceAddress the target device address.
|
||||
* @param error error number.
|
||||
* @param errorType the error type, one of
|
||||
* {@link DfuBaseService#ERROR_TYPE_COMMUNICATION_STATE},
|
||||
* {@link DfuBaseService#ERROR_TYPE_COMMUNICATION},
|
||||
* {@link DfuBaseService#ERROR_TYPE_DFU_REMOTE},
|
||||
* {@link DfuBaseService#ERROR_TYPE_OTHER}.
|
||||
* @param message the error message.
|
||||
*/
|
||||
void onError(final String deviceAddress, final int error, final int errorType, final String message);
|
||||
void onError(@NonNull final String deviceAddress,
|
||||
final int error, final int errorType, final String message);
|
||||
}
|
||||
|
|
|
@ -22,39 +22,44 @@
|
|||
|
||||
package no.nordicsemi.android.dfu;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
public class DfuProgressListenerAdapter implements DfuProgressListener {
|
||||
|
||||
@Override
|
||||
public void onDeviceConnecting(final String deviceAddress) {
|
||||
public void onDeviceConnecting(@NonNull final String deviceAddress) {
|
||||
// empty default implementation
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDeviceConnected(final String deviceAddress) {
|
||||
public void onDeviceConnected(@NonNull final String deviceAddress) {
|
||||
// empty default implementation
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDfuProcessStarting(final String deviceAddress) {
|
||||
public void onDfuProcessStarting(@NonNull final String deviceAddress) {
|
||||
// empty default implementation
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDfuProcessStarted(final String deviceAddress) {
|
||||
public void onDfuProcessStarted(@NonNull final String deviceAddress) {
|
||||
// empty default implementation
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onEnablingDfuMode(final String deviceAddress) {
|
||||
public void onEnablingDfuMode(@NonNull final String deviceAddress) {
|
||||
// empty default implementation
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onProgressChanged(final String deviceAddress, final int percent, final float speed, final float avgSpeed, final int currentPart, final int partsTotal) {
|
||||
public void onProgressChanged(@NonNull final String deviceAddress, final int percent,
|
||||
final float speed, final float avgSpeed,
|
||||
final int currentPart, final int partsTotal) {
|
||||
// empty default implementation
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFirmwareValidating(final String deviceAddress) {
|
||||
public void onFirmwareValidating(@NonNull final String deviceAddress) {
|
||||
// empty default implementation
|
||||
}
|
||||
|
||||
|
@ -64,22 +69,23 @@ public class DfuProgressListenerAdapter implements DfuProgressListener {
|
|||
}
|
||||
|
||||
@Override
|
||||
public void onDeviceDisconnected(final String deviceAddress) {
|
||||
public void onDeviceDisconnected(@NonNull final String deviceAddress) {
|
||||
// empty default implementation
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDfuCompleted(final String deviceAddress) {
|
||||
public void onDfuCompleted(@NonNull final String deviceAddress) {
|
||||
// empty default implementation
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDfuAborted(final String deviceAddress) {
|
||||
public void onDfuAborted(@NonNull final String deviceAddress) {
|
||||
// empty default implementation
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(final String deviceAddress, final int error, final int errorType, final String message) {
|
||||
public void onError(@NonNull final String deviceAddress,
|
||||
final int error, final int errorType, final String message) {
|
||||
// empty default implementation
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
package no.nordicsemi.android.dfu;
|
||||
|
||||
import androidx.annotation.IntDef;
|
||||
|
||||
@SuppressWarnings("WeakerAccess")
|
||||
@IntDef(value = {
|
||||
DfuServiceInitiator.SCOPE_SYSTEM_COMPONENTS,
|
||||
DfuServiceInitiator.SCOPE_APPLICATION
|
||||
},
|
||||
flag = true)
|
||||
public @interface DfuScope {}
|
|
@ -27,24 +27,55 @@ import android.content.Intent;
|
|||
|
||||
import java.io.InputStream;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import no.nordicsemi.android.dfu.internal.exception.DeviceDisconnectedException;
|
||||
import no.nordicsemi.android.dfu.internal.exception.DfuException;
|
||||
import no.nordicsemi.android.dfu.internal.exception.UploadAbortedException;
|
||||
|
||||
/* package */ interface DfuService extends DfuCallback {
|
||||
|
||||
/** This method must return true if the device is compatible with this DFU implementation, false otherwise. */
|
||||
boolean isClientCompatible(final Intent intent, final BluetoothGatt gatt) throws DfuException, DeviceDisconnectedException, UploadAbortedException;
|
||||
/**
|
||||
* This method must return true if the device is compatible with this DFU implementation,
|
||||
* false otherwise.
|
||||
*
|
||||
* @throws DeviceDisconnectedException Thrown when the device will disconnect in the middle of
|
||||
* the transmission.
|
||||
* @throws DfuException Thrown if DFU error occur.
|
||||
* @throws UploadAbortedException Thrown if DFU operation was aborted by user.
|
||||
*/
|
||||
boolean isClientCompatible(@NonNull final Intent intent, @NonNull final BluetoothGatt gatt)
|
||||
throws DfuException, DeviceDisconnectedException, UploadAbortedException;
|
||||
|
||||
/**
|
||||
* Initializes the DFU implementation and does some initial setting up.
|
||||
* @return true if initialization was successful and the DFU process may begin, false to finish teh DFU service
|
||||
*
|
||||
* @return True if initialization was successful and the DFU process may begin,
|
||||
* false to finish teh DFU service.
|
||||
* @throws DeviceDisconnectedException Thrown when the device will disconnect in the middle of
|
||||
* the transmission.
|
||||
* @throws DfuException Thrown if DFU error occur.
|
||||
* @throws UploadAbortedException Thrown if DFU operation was aborted by user.
|
||||
*/
|
||||
boolean initialize(final Intent intent, final BluetoothGatt gatt, final int fileType, final InputStream firmwareStream, final InputStream initPacketStream) throws DfuException, DeviceDisconnectedException, UploadAbortedException;
|
||||
boolean initialize(@NonNull final Intent intent, @NonNull final BluetoothGatt gatt,
|
||||
@FileType final int fileType,
|
||||
@NonNull final InputStream firmwareStream,
|
||||
@Nullable final InputStream initPacketStream)
|
||||
throws DfuException, DeviceDisconnectedException, UploadAbortedException;
|
||||
|
||||
/** Performs the DFU process. */
|
||||
void performDfu(final Intent intent) throws DfuException, DeviceDisconnectedException, UploadAbortedException;
|
||||
/**
|
||||
* Performs the DFU process.
|
||||
*
|
||||
* @throws DeviceDisconnectedException Thrown when the device will disconnect in the middle of
|
||||
* the transmission.
|
||||
* @throws DfuException Thrown if DFU error occur.
|
||||
* @throws UploadAbortedException Thrown if DFU operation was aborted by user.
|
||||
*/
|
||||
void performDfu(@NonNull final Intent intent)
|
||||
throws DfuException, DeviceDisconnectedException, UploadAbortedException;
|
||||
|
||||
/** Releases the service. */
|
||||
/**
|
||||
* Releases the service.
|
||||
*/
|
||||
void release();
|
||||
}
|
||||
|
|
|
@ -24,13 +24,18 @@ package no.nordicsemi.android.dfu;
|
|||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
|
||||
|
||||
/**
|
||||
* A controller class allows you to pause, resume or abort the DFU operation in a easy way.
|
||||
* <p>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.</p>
|
||||
* <p>Added in DFU Library version 1.0.2.</p>
|
||||
* <p>
|
||||
* 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.
|
||||
* <p>
|
||||
* 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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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.
|
||||
* <p>
|
||||
* If the value given is equal to 0, the {@link #DEFAULT_PRN_VALUE} will be used instead.
|
||||
* <p>
|
||||
* 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.
|
||||
* <p>
|
||||
* 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.
|
||||
* <p>
|
||||
* 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.
|
||||
* <p>
|
||||
* 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.
|
||||
* <p>
|
||||
* 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.
|
||||
* <p>
|
||||
* 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<br>
|
||||
* The device should disconnect and restart in DFU mode after sending the notification.
|
||||
* <p>
|
||||
* 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;
|
||||
|
|
|
@ -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.
|
||||
*
|
||||
* <p>Use {@link #registerProgressListener(Context, DfuProgressListener)} or {@link #registerLogListener(Context, DfuLogListener)} to register your listeners. Remember about unregistering them
|
||||
* when your context is destroyed.</p>
|
||||
* 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.
|
||||
* <p>
|
||||
* 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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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.
|
||||
* <p>
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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 {}
|
||||
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* 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]>
|
||||
* </p>
|
||||
* 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]>
|
||||
* <p>
|
||||
* 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.
|
||||
* </p>
|
||||
* 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);
|
||||
|
|
|
@ -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:
|
||||
* <ol>
|
||||
* <li>Selects the Command object - this Op Code returns the maximum acceptable size of a command object, and the offset and CRC32 of the command
|
||||
* that is already stored in the device (in case the DFU was started in a previous connection and disconnected before it has finished).</li>
|
||||
* <li>If the offset received is greater than 0 and less or equal to the size of the Init file that is to be sent, it will compare the
|
||||
* <li>Selects the Command object - this Op Code returns the maximum acceptable size of a
|
||||
* command object, and the offset and CRC32 of the command that is already stored in the
|
||||
* device (in case the DFU was started in a previous connection and disconnected before
|
||||
* it has finished).</li>
|
||||
* <li>If the offset received is greater than 0 and less or equal to the size of the
|
||||
* Init file that is to be sent, it will compare the
|
||||
* received CRC with the local one and, if they match:
|
||||
* <ul>
|
||||
* <li>If offset < init file size - it will continue sending the Init file from the point it stopped before,</li>
|
||||
* <li>If offset == init file size - it will send Execute command to execute the Init file, as it may have not been executed before.</li>
|
||||
* <li>If offset < init file size - it will continue sending the Init file from the
|
||||
* point it stopped before,</li>
|
||||
* <li>If offset == init file size - it will send Execute command to execute the
|
||||
* Init file, as it may have not been executed before.</li>
|
||||
* </ul>
|
||||
* </li>
|
||||
* <li>If the CRC don't match, or the received offset is greater then init file size, it creates the Command Object and sends the whole
|
||||
* Init file as the previous one was different.</li>
|
||||
* <li>If the CRC don't match, or the received offset is greater then init file size,
|
||||
* it creates the Command Object and sends the whole Init file as the previous one was
|
||||
* different.</li>
|
||||
* </ol>
|
||||
* Sending of the Init packet is done without using PRNs (Packet Receipt Notifications), so they are disabled prior to sending the data.
|
||||
* Sending of the Init packet is done without using PRNs (Packet Receipt Notifications),
|
||||
* so they are disabled prior to sending the data.
|
||||
*
|
||||
* @param gatt the target GATT device
|
||||
* @param allowResume true to allow resuming sending Init Packet. If false, it will be started again.
|
||||
* @param gatt the target GATT device.
|
||||
* @param allowResume true to allow resuming sending Init Packet. If false, it will be started
|
||||
* again.
|
||||
* @throws RemoteDfuException
|
||||
* @throws DeviceDisconnectedException
|
||||
* @throws DfuException
|
||||
* @throws UploadAbortedException
|
||||
* @throws UnknownResponseException
|
||||
*/
|
||||
private void sendInitPacket(final BluetoothGatt gatt, final boolean allowResume) throws RemoteDfuException, DeviceDisconnectedException, DfuException, UploadAbortedException, UnknownResponseException {
|
||||
private void sendInitPacket(@NonNull final BluetoothGatt gatt, final boolean allowResume)
|
||||
throws RemoteDfuException, DeviceDisconnectedException, DfuException, UploadAbortedException, UnknownResponseException {
|
||||
final CRC32 crc32 = new CRC32(); // Used to calculate CRC32 of the Init packet
|
||||
ObjectChecksum checksum;
|
||||
|
||||
|
@ -325,6 +349,7 @@ class SecureDfuImpl extends BaseCustomDfuImpl {
|
|||
final ObjectInfo info = selectObject(OBJECT_COMMAND);
|
||||
logi(String.format(Locale.US, "Command object info received (Max size = %d, Offset = %d, CRC = %08X)", info.maxSize, info.offset, info.CRC32));
|
||||
mService.sendLogBroadcast(DfuBaseService.LOG_LEVEL_APPLICATION, String.format(Locale.US, "Command object info received (Max size = %d, Offset = %d, CRC = %08X)", info.maxSize, info.offset, info.CRC32));
|
||||
//noinspection StatementWithEmptyBody
|
||||
if (mInitPacketSizeInBytes > info.maxSize) {
|
||||
// Ignore this here. Later, after sending the 'Create object' command, DFU target will send an error if init packet is too large
|
||||
}
|
||||
|
@ -391,8 +416,13 @@ class SecureDfuImpl extends BaseCustomDfuImpl {
|
|||
mService.sendLogBroadcast(DfuBaseService.LOG_LEVEL_APPLICATION, "Command object created");
|
||||
}
|
||||
// Write Init data to the Packet Characteristic
|
||||
logi("Sending " + (mInitPacketSizeInBytes - info.offset) + " bytes of init packet...");
|
||||
writeInitData(mPacketCharacteristic, crc32);
|
||||
try {
|
||||
logi("Sending " + (mInitPacketSizeInBytes - info.offset) + " bytes of init packet...");
|
||||
writeInitData(mPacketCharacteristic, crc32);
|
||||
} catch (final DeviceDisconnectedException e) {
|
||||
loge("Disconnected while sending init packet");
|
||||
throw e;
|
||||
}
|
||||
final int crc = (int) (crc32.getValue() & 0xFFFFFFFFL);
|
||||
mService.sendLogBroadcast(DfuBaseService.LOG_LEVEL_APPLICATION, String.format(Locale.US, "Command object sent (CRC = %08X)", crc));
|
||||
|
||||
|
@ -442,33 +472,41 @@ class SecureDfuImpl extends BaseCustomDfuImpl {
|
|||
* This method does the following:
|
||||
* <ol>
|
||||
* <li>Sets the Packet Receipt Notification to a value specified in the settings.</li>
|
||||
* <li>Selects the Data object - this returns maximum single object size and the offset and CRC of the data already saved.</li>
|
||||
* <li>If the offset received is greater than 0 it will calculate the CRC of the same number of bytes of the firmware to be sent.
|
||||
* If the CRC match it will continue sending data. Otherwise, it will go back to the beginning of the last chunk, or to the beginning
|
||||
* of the previous chunk assuming the last one was not executed before, and continue sending data from there.</li>
|
||||
* <li>If the CRC and offset received match and the offset is equal to the firmware size, it will only send the Execute command.</li>
|
||||
* <li>Selects the Data object - this returns maximum single object size and the offset
|
||||
* and CRC of the data already saved.</li>
|
||||
* <li>If the offset received is greater than 0 it will calculate the CRC of the same
|
||||
* number of bytes of the firmware to be sent. If the CRC match it will continue sending data.
|
||||
* Otherwise, it will go back to the beginning of the last chunk, or to the beginning
|
||||
* of the previous chunk assuming the last one was not executed before, and continue
|
||||
* sending data from there.</li>
|
||||
* <li>If the CRC and offset received match and the offset is equal to the firmware size,
|
||||
* it will only send the Execute command.</li>
|
||||
* </ol>
|
||||
* @param gatt the target GATT device
|
||||
* @param gatt the target GATT device.
|
||||
* @throws RemoteDfuException
|
||||
* @throws DeviceDisconnectedException
|
||||
* @throws DfuException
|
||||
* @throws UploadAbortedException
|
||||
* @throws UnknownResponseException
|
||||
*/
|
||||
private void sendFirmware(final BluetoothGatt gatt) throws RemoteDfuException, DeviceDisconnectedException, DfuException, UploadAbortedException, UnknownResponseException {
|
||||
private void sendFirmware(final BluetoothGatt gatt) throws RemoteDfuException,
|
||||
DeviceDisconnectedException, DfuException, UploadAbortedException, UnknownResponseException {
|
||||
// Send the number of packets of firmware before receiving a receipt notification
|
||||
int numberOfPacketsBeforeNotification = mPacketsBeforeNotification;
|
||||
if (numberOfPacketsBeforeNotification > 0) {
|
||||
setPacketReceiptNotifications(numberOfPacketsBeforeNotification);
|
||||
mService.sendLogBroadcast(DfuBaseService.LOG_LEVEL_APPLICATION, "Packet Receipt Notif Req (Op Code = 2) sent (Value = " + numberOfPacketsBeforeNotification + ")");
|
||||
mService.sendLogBroadcast(DfuBaseService.LOG_LEVEL_APPLICATION,
|
||||
"Packet Receipt Notif Req (Op Code = 2) sent (Value = " + numberOfPacketsBeforeNotification + ")");
|
||||
}
|
||||
|
||||
// We are ready to start sending the new firmware.
|
||||
|
||||
logi("Setting object to Data (Op Code = 6, Type = 2)");
|
||||
final ObjectInfo info = selectObject(OBJECT_DATA);
|
||||
logi(String.format(Locale.US, "Data object info received (Max size = %d, Offset = %d, CRC = %08X)", info.maxSize, info.offset, info.CRC32));
|
||||
mService.sendLogBroadcast(DfuBaseService.LOG_LEVEL_APPLICATION, String.format(Locale.US, "Data object info received (Max size = %d, Offset = %d, CRC = %08X)", info.maxSize, info.offset, info.CRC32));
|
||||
logi(String.format(Locale.US,
|
||||
"Data object info received (Max size = %d, Offset = %d, CRC = %08X)", info.maxSize, info.offset, info.CRC32));
|
||||
mService.sendLogBroadcast(DfuBaseService.LOG_LEVEL_APPLICATION, String.format(Locale.US,
|
||||
"Data object info received (Max size = %d, Offset = %d, CRC = %08X)", info.maxSize, info.offset, info.CRC32));
|
||||
mProgressInfo.setMaxObjectSizeInBytes(info.maxSize);
|
||||
|
||||
// Number of chunks in which the data will be sent
|
||||
|
@ -503,7 +541,8 @@ class SecureDfuImpl extends BaseCustomDfuImpl {
|
|||
|
||||
if (crc == info.CRC32) {
|
||||
logi(info.offset + " bytes of data sent before, CRC match");
|
||||
mService.sendLogBroadcast(DfuBaseService.LOG_LEVEL_APPLICATION, info.offset + " bytes of data sent before, CRC match");
|
||||
mService.sendLogBroadcast(DfuBaseService.LOG_LEVEL_APPLICATION,
|
||||
info.offset + " bytes of data sent before, CRC match");
|
||||
mProgressInfo.setBytesSent(info.offset);
|
||||
mProgressInfo.setBytesReceived(info.offset);
|
||||
|
||||
|
@ -517,7 +556,8 @@ class SecureDfuImpl extends BaseCustomDfuImpl {
|
|||
}
|
||||
} else {
|
||||
logi(info.offset + " bytes sent before, CRC does not match");
|
||||
mService.sendLogBroadcast(DfuBaseService.LOG_LEVEL_WARNING, info.offset + " bytes sent before, CRC does not match");
|
||||
mService.sendLogBroadcast(DfuBaseService.LOG_LEVEL_WARNING,
|
||||
info.offset + " bytes sent before, CRC does not match");
|
||||
// The CRC of the current object is not correct. If there was another Data object sent before, its CRC must have been correct,
|
||||
// as it has been executed. Either way, we have to create the current object again.
|
||||
mProgressInfo.setBytesSent(bytesSentAndExecuted);
|
||||
|
@ -526,7 +566,8 @@ class SecureDfuImpl extends BaseCustomDfuImpl {
|
|||
info.CRC32 = 0; // invalidate
|
||||
mFirmwareStream.reset();
|
||||
logi("Resuming from byte " + info.offset + "...");
|
||||
mService.sendLogBroadcast(DfuBaseService.LOG_LEVEL_APPLICATION, "Resuming from byte " + info.offset + "...");
|
||||
mService.sendLogBroadcast(DfuBaseService.LOG_LEVEL_APPLICATION,
|
||||
"Resuming from byte " + info.offset + "...");
|
||||
}
|
||||
} catch (final IOException e) {
|
||||
loge("Error while reading firmware stream", e);
|
||||
|
@ -549,10 +590,13 @@ class SecureDfuImpl extends BaseCustomDfuImpl {
|
|||
final int availableObjectSizeInBytes = mProgressInfo.getAvailableObjectSizeIsBytes();
|
||||
logi("Creating Data object (Op Code = 1, Type = 2, Size = " + availableObjectSizeInBytes + ") (" + (currentChunk + 1) + "/" + chunkCount + ")");
|
||||
writeCreateRequest(OBJECT_DATA, availableObjectSizeInBytes);
|
||||
mService.sendLogBroadcast(DfuBaseService.LOG_LEVEL_APPLICATION, "Data object (" + (currentChunk + 1) + "/" + chunkCount + ") created");
|
||||
mService.sendLogBroadcast(DfuBaseService.LOG_LEVEL_APPLICATION, "Uploading firmware...");
|
||||
mService.sendLogBroadcast(DfuBaseService.LOG_LEVEL_APPLICATION,
|
||||
"Data object (" + (currentChunk + 1) + "/" + chunkCount + ") created");
|
||||
mService.sendLogBroadcast(DfuBaseService.LOG_LEVEL_APPLICATION,
|
||||
"Uploading firmware...");
|
||||
} else {
|
||||
mService.sendLogBroadcast(DfuBaseService.LOG_LEVEL_APPLICATION, "Resuming uploading firmware...");
|
||||
mService.sendLogBroadcast(DfuBaseService.LOG_LEVEL_APPLICATION,
|
||||
"Resuming uploading firmware...");
|
||||
resumeSendingData = false;
|
||||
}
|
||||
|
||||
|
@ -569,13 +613,15 @@ class SecureDfuImpl extends BaseCustomDfuImpl {
|
|||
logi("Sending Calculate Checksum command (Op Code = 3)");
|
||||
final ObjectChecksum checksum = readChecksum();
|
||||
logi(String.format(Locale.US, "Checksum received (Offset = %d, CRC = %08X)", checksum.offset, checksum.CRC32));
|
||||
mService.sendLogBroadcast(DfuBaseService.LOG_LEVEL_APPLICATION, String.format(Locale.US, "Checksum received (Offset = %d, CRC = %08X)", checksum.offset, checksum.CRC32));
|
||||
mService.sendLogBroadcast(DfuBaseService.LOG_LEVEL_APPLICATION, String.format(Locale.US,
|
||||
"Checksum received (Offset = %d, CRC = %08X)", checksum.offset, checksum.CRC32));
|
||||
|
||||
// It may happen, that not all bytes that were sent were received by the remote device
|
||||
final int bytesLost = mProgressInfo.getBytesSent() - checksum.offset;
|
||||
if (bytesLost > 0) {
|
||||
logw(bytesLost + " bytes were lost!");
|
||||
mService.sendLogBroadcast(DfuBaseService.LOG_LEVEL_WARNING, bytesLost + " bytes were lost");
|
||||
mService.sendLogBroadcast(DfuBaseService.LOG_LEVEL_WARNING,
|
||||
bytesLost + " bytes were lost");
|
||||
|
||||
try {
|
||||
// We have to reset the stream and read 'offset' number of bytes to recalculate the CRC
|
||||
|
@ -590,7 +636,8 @@ class SecureDfuImpl extends BaseCustomDfuImpl {
|
|||
// To decrease the chance of loosing data next time let's set PRN to 1. This will make the update very long, but perhaps it will succeed.
|
||||
numberOfPacketsBeforeNotification = mPacketsBeforeNotification = 1;
|
||||
setPacketReceiptNotifications(numberOfPacketsBeforeNotification);
|
||||
mService.sendLogBroadcast(DfuBaseService.LOG_LEVEL_APPLICATION, "Packet Receipt Notif Req (Op Code = 2) sent (Value = " + numberOfPacketsBeforeNotification + ")");
|
||||
mService.sendLogBroadcast(DfuBaseService.LOG_LEVEL_APPLICATION,
|
||||
"Packet Receipt Notif Req (Op Code = 2) sent (Value = " + numberOfPacketsBeforeNotification + ")");
|
||||
}
|
||||
|
||||
// Calculate the CRC32
|
||||
|
@ -673,9 +720,10 @@ class SecureDfuImpl extends BaseCustomDfuImpl {
|
|||
* Sets number of data packets that will be send before the notification will be received.
|
||||
*
|
||||
* @param data control point data packet
|
||||
* @param value number of packets before receiving notification. If this value is 0, then the notification of packet receipt will be disabled by the DFU target.
|
||||
* @param value number of packets before receiving notification. If this value is 0, then the
|
||||
* notification of packet receipt will be disabled by the DFU target.
|
||||
*/
|
||||
private void setNumberOfPackets(final byte[] data, final int value) {
|
||||
private void setNumberOfPackets(@SuppressWarnings("SameParameterValue") @NonNull final byte[] data, final int value) {
|
||||
data[1] = (byte) (value & 0xFF);
|
||||
data[2] = (byte) ((value >> 8) & 0xFF);
|
||||
}
|
||||
|
@ -686,7 +734,7 @@ class SecureDfuImpl extends BaseCustomDfuImpl {
|
|||
* @param data control point data packet
|
||||
* @param value Object size in bytes.
|
||||
*/
|
||||
private void setObjectSize(final byte[] data, final int value) {
|
||||
private void setObjectSize(@NonNull final byte[] data, final int value) {
|
||||
data[2] = (byte) (value & 0xFF);
|
||||
data[3] = (byte) ((value >> 8) & 0xFF);
|
||||
data[4] = (byte) ((value >> 16) & 0xFF);
|
||||
|
@ -694,20 +742,23 @@ class SecureDfuImpl extends BaseCustomDfuImpl {
|
|||
}
|
||||
|
||||
/**
|
||||
* Sets the number of packets that needs to be sent to receive the Packet Receipt Notification. Value 0 disables PRNs.
|
||||
* By default this is disabled. The PRNs may be used to send both the Data and Command object, but this Secure DFU implementation
|
||||
* can handle them only during Data transfer.<br/>
|
||||
* The intention of having PRNs is to make sure the outgoing BLE buffer is not getting overflown. The PRN will be sent after sending all
|
||||
* packets from the queue.
|
||||
* Sets the number of packets that needs to be sent to receive the Packet Receipt Notification.
|
||||
* Value 0 disables PRNs. By default this is disabled. The PRNs may be used to send both the
|
||||
* Data and Command object, but this Secure DFU implementation can handle them only during Data transfer.
|
||||
* <p>
|
||||
* 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.</li>
|
||||
* </ol>
|
||||
* See: <a href="https://github.com/NordicSemiconductor/Android-DFU-Library/issues/86">https://github.com/NordicSemiconductor/Android-DFU-Library/issues/86</a>
|
||||
* @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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
|
||||
/**
|
||||
* <p>Reads the firmware files from the a ZIP file. The ZIP file must be either created using the <b>nrf utility</b> 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.</p>
|
||||
* <p>The ArchiveInputStream will read only files with types specified by <b>types</b> parameter of the constructor.</p>
|
||||
* <p>
|
||||
* Reads the firmware files from the a ZIP file. The ZIP file must be either created using the
|
||||
* <a href="https://github.com/NordicSemiconductor/pc-nrfutil"><b>nrf util</b></a>,
|
||||
* 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.
|
||||
* <p>
|
||||
* The ArchiveInputStream will read only files with types specified by <b>types</b> 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<String, byte[]> entries;
|
||||
private Manifest manifest;
|
||||
private CRC32 crc32;
|
||||
|
@ -91,12 +101,13 @@ public class ArchiveInputStream extends InputStream {
|
|||
|
||||
/**
|
||||
* <p>
|
||||
* 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.
|
||||
* </p>
|
||||
* <p>Use bit combination of the following types:</p>
|
||||
* <p>
|
||||
* Use bit combination of the following types:
|
||||
* <ul>
|
||||
* <li>{@link DfuBaseService#TYPE_SOFT_DEVICE}</li>
|
||||
* <li>{@link DfuBaseService#TYPE_BOOTLOADER}</li>
|
||||
|
@ -104,15 +115,15 @@ public class ArchiveInputStream extends InputStream {
|
|||
* <li>{@link DfuBaseService#TYPE_AUTO}</li>
|
||||
* </ul>
|
||||
*
|
||||
* @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).
|
||||
* <p>
|
||||
* The ZIP file is valid when contains a 'manifest.json' file and all BIN and DAT files that
|
||||
* are specified in the manifest.
|
||||
* <p>
|
||||
* 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 <code>null</code> if the last file has been transmitted.
|
||||
* Sets the currentSource to the new file or to <code>null</code> 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
|
||||
* <code>null</code> 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
|
||||
* <code>null</code> 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 <code>null</code> 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 <code>null</code> 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();
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -22,6 +22,7 @@
|
|||
|
||||
package no.nordicsemi.android.dfu.internal.manifest;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public class ManifestFile {
|
||||
private Manifest manifest;
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue