Skip to content

Commit 72fd866

Browse files
authored
Merge pull request #469 from NordicSemiconductor/improvements
Various improvements
2 parents fb578b2 + c7bf6c6 commit 72fd866

File tree

8 files changed

+105
-40
lines changed

8 files changed

+105
-40
lines changed

lib/dfu/src/main/java/no/nordicsemi/android/dfu/BaseCustomDfuImpl.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -354,7 +354,7 @@ private void writeInitPacket(final BluetoothGattCharacteristic characteristic, f
354354
loge("Sleeping interrupted", e);
355355
}
356356
if (!mConnected)
357-
throw new DeviceDisconnectedException("Unable to write Init DFU Parameters: device disconnected");
357+
throw new DeviceDisconnectedException("Unable to write Init DFU Parameters: device disconnected", mError);
358358
if (mError != 0)
359359
throw new DfuException("Unable to write Init DFU Parameters", mError);
360360
}
@@ -404,7 +404,7 @@ void uploadFirmwareImage(final BluetoothGattCharacteristic packetCharacteristic)
404404
}
405405

406406
if (!mConnected)
407-
throw new DeviceDisconnectedException("Uploading Firmware Image failed: device disconnected");
407+
throw new DeviceDisconnectedException("Uploading Firmware Image failed: device disconnected", mError);
408408
if (mError != 0)
409409
throw new DfuException("Uploading Firmware Image failed", mError);
410410
}

lib/dfu/src/main/java/no/nordicsemi/android/dfu/BaseDfuImpl.java

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -120,8 +120,9 @@ protected class BaseBluetoothGattCallback extends DfuGattCallback {
120120
// public void onConnected() { }
121121

122122
@Override
123-
public void onDisconnected() {
123+
public void onDisconnected(int error) {
124124
mConnected = false;
125+
mError = error;
125126
notifyLock();
126127
}
127128

@@ -452,7 +453,7 @@ void enableCCCD(@NonNull final BluetoothGattCharacteristic characteristic, final
452453
final BluetoothGatt gatt = mGatt;
453454
final String debugString = type == NOTIFICATIONS ? "notifications" : "indications";
454455
if (!mConnected)
455-
throw new DeviceDisconnectedException("Unable to set " + debugString + " state: device disconnected");
456+
throw new DeviceDisconnectedException("Unable to set " + debugString + " state: device disconnected", mError);
456457
if (mAborted)
457458
throw new UploadAbortedException();
458459

@@ -491,7 +492,7 @@ void enableCCCD(@NonNull final BluetoothGattCharacteristic characteristic, final
491492
loge("Sleeping interrupted", e);
492493
}
493494
if (!mConnected)
494-
throw new DeviceDisconnectedException("Unable to set " + debugString + " state: device disconnected");
495+
throw new DeviceDisconnectedException("Unable to set " + debugString + " state: device disconnected", mError);
495496
if (mError != 0)
496497
throw new DfuException("Unable to set " + debugString + " state", mError);
497498
}
@@ -508,7 +509,7 @@ void enableCCCD(@NonNull final BluetoothGattCharacteristic characteristic, final
508509
private boolean isServiceChangedCCCDEnabled()
509510
throws DeviceDisconnectedException, DfuException, UploadAbortedException {
510511
if (!mConnected)
511-
throw new DeviceDisconnectedException("Unable to read Service Changed CCCD: device disconnected");
512+
throw new DeviceDisconnectedException("Unable to read Service Changed CCCD: device disconnected", mError);
512513
if (mAborted)
513514
throw new UploadAbortedException();
514515

@@ -545,7 +546,7 @@ private boolean isServiceChangedCCCDEnabled()
545546
loge("Sleeping interrupted", e);
546547
}
547548
if (!mConnected)
548-
throw new DeviceDisconnectedException("Unable to read Service Changed CCCD: device disconnected");
549+
throw new DeviceDisconnectedException("Unable to read Service Changed CCCD: device disconnected", mError);
549550
if (mError != 0)
550551
throw new DfuException("Unable to read Service Changed CCCD", mError);
551552

@@ -608,7 +609,7 @@ void writeOpCode(@NonNull final BluetoothGattCharacteristic characteristic, @Non
608609
loge("Sleeping interrupted", e);
609610
}
610611
if (!mResetRequestSent && !mConnected)
611-
throw new DeviceDisconnectedException("Unable to write Op Code " + value[0] + ": device disconnected");
612+
throw new DeviceDisconnectedException("Unable to write Op Code " + value[0] + ": device disconnected", mError);
612613
if (!mResetRequestSent && mError != 0)
613614
throw new DfuException("Unable to write Op Code " + value[0], mError);
614615
}
@@ -663,7 +664,7 @@ private boolean createBondApi18(@NonNull final BluetoothDevice device) {
663664
//noinspection ConstantConditions
664665
return (Boolean) createBond.invoke(device);
665666
} catch (final Exception e) {
666-
Log.w(TAG, "An exception occurred while creating bond", e);
667+
loge("An exception occurred while creating bond", e);
667668
}
668669
return false;
669670
}
@@ -692,6 +693,7 @@ boolean removeBond() {
692693
mService.sendLogBroadcast(DfuBaseService.LOG_LEVEL_DEBUG, "gatt.getDevice().removeBond() (hidden)");
693694
//noinspection ConstantConditions
694695
result = (Boolean) removeBond.invoke(device);
696+
logw("Bond information " + (result ? "removed" : "NOT removed"));
695697

696698
// We have to wait until device is unbounded
697699
try {
@@ -703,7 +705,7 @@ boolean removeBond() {
703705
loge("Sleeping interrupted", e);
704706
}
705707
} catch (final Exception e) {
706-
Log.w(TAG, "An exception occurred while removing bond information", e);
708+
loge("An exception occurred while removing bond information", e);
707709
}
708710
return result;
709711
}
@@ -767,7 +769,7 @@ void requestMtu(@IntRange(from = 0, to = 517) final int mtu)
767769
loge("Sleeping interrupted", e);
768770
}
769771
if (!mConnected)
770-
throw new DeviceDisconnectedException("Unable to read Service Changed CCCD: device disconnected");
772+
throw new DeviceDisconnectedException("Unable to request MTU: device disconnected", mError);
771773
}
772774

773775
/**
@@ -795,9 +797,9 @@ byte[] readNotificationResponse()
795797
if (mAborted)
796798
throw new UploadAbortedException();
797799
if (!mConnected)
798-
throw new DeviceDisconnectedException("Unable to write Op Code: device disconnected");
800+
throw new DeviceDisconnectedException("Response not received: device disconnected", mError);
799801
if (mError != 0)
800-
throw new DfuException("Unable to write Op Code", mError);
802+
throw new DfuException("Response not received", mError);
801803
return mReceivedData;
802804
}
803805

lib/dfu/src/main/java/no/nordicsemi/android/dfu/DfuBaseService.java

Lines changed: 45 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -829,7 +829,7 @@ public void onReceive(final Context context, final Intent intent) {
829829
sendLogBroadcast(LOG_LEVEL_WARNING, "Bluetooth adapter disabled");
830830
mConnectionState = STATE_DISCONNECTED;
831831
if (mDfuServiceImpl != null)
832-
mDfuServiceImpl.getGattCallback().onDisconnected();
832+
mDfuServiceImpl.getGattCallback().onDisconnected(22 /* GATT CONN TERMINATE LOCAL HOST */);
833833

834834
// Notify waiting thread
835835
synchronized (mLock) {
@@ -903,18 +903,24 @@ public void onConnectionStateChange(final BluetoothGatt gatt, final int status,
903903
* On bonded devices the Service Changed indication will be sent to
904904
* indicate that the services has changed.
905905
*
906-
* The code below will wait 1.6 seconds for the indication, or continue
906+
* The code below will wait 4 seconds for the indication, or continue
907907
* with service discovery immediately when the indication is received.
908908
* See "onServiceChanged" method below.
909+
*
910+
* It was tested using Pixel 7 with Android 15 using SDK 11 and 17.1, that
911+
* at least around 4 seconds are required. When service discovery is started
912+
* before SC indication is received, following operations will fail.
913+
* On SDK 11 the SC indication is received after service discovery is started,
914+
* but this seems not to cause any issues.
909915
*/
910916
if (gatt.getDevice().getBondState() == BluetoothDevice.BOND_BONDED) {
911-
logi("Waiting 1600 ms for a possible Service Changed indication...");
917+
logi("Waiting 4000 ms for a possible Service Changed indication...");
912918
mHandler.postDelayed(() -> {
913919
if (mConnectionState != STATE_CONNECTING)
914920
return;
915921
mConnectionState = STATE_CONNECTED;
916922
discoverServices(gatt);
917-
}, 1600);
923+
}, 4000);
918924
} else {
919925
mConnectionState = STATE_CONNECTED;
920926
discoverServices(gatt);
@@ -924,7 +930,7 @@ public void onConnectionStateChange(final BluetoothGatt gatt, final int status,
924930
logi("Disconnected from GATT server");
925931
mConnectionState = STATE_DISCONNECTED;
926932
if (mDfuServiceImpl != null)
927-
mDfuServiceImpl.getGattCallback().onDisconnected();
933+
mDfuServiceImpl.getGattCallback().onDisconnected(0);
928934
}
929935
} else {
930936
if (status == 0x08 /* GATT CONN TIMEOUT */ || status == 0x13 /* GATT CONN TERMINATE PEER USER */)
@@ -935,7 +941,7 @@ public void onConnectionStateChange(final BluetoothGatt gatt, final int status,
935941
if (newState == BluetoothGatt.STATE_DISCONNECTED) {
936942
mConnectionState = STATE_DISCONNECTED;
937943
if (mDfuServiceImpl != null)
938-
mDfuServiceImpl.getGattCallback().onDisconnected();
944+
mDfuServiceImpl.getGattCallback().onDisconnected(mError);
939945
}
940946
}
941947

@@ -961,6 +967,13 @@ public void onServicesDiscovered(final BluetoothGatt gatt, final int status) {
961967
}
962968
}
963969

970+
// Note: This method was added in Android 11. Earlier versions will handle service change
971+
// internally, but will not notify the app, so the app will always wait for 4 seconds
972+
// before starting service discovery.
973+
// Added here:
974+
// https://cs.android.com/android/_/android/platform/packages/modules/Bluetooth/+/d173ec435715533f4c9fd3d104df70afae8826d8
975+
// Exposed here:
976+
// https://cs.android.com/android/_/android/platform/packages/modules/Bluetooth/+/f36b9b5d686e8bf02a1d9fd482324037ebf2310f
964977
@Override
965978
public void onServiceChanged(@NonNull final BluetoothGatt gatt) {
966979
if (mConnectionState != STATE_CONNECTING)
@@ -1365,9 +1378,10 @@ protected void onHandleIntent(@Nullable final Intent intent) {
13651378
// Connection usually fails due to a 133 error (device unreachable, or.. something else went wrong).
13661379
// Usually trying the same for the second time works. Let's try 2 times.
13671380
final int attempt = intent.getIntExtra(EXTRA_RECONNECTION_ATTEMPT, 0);
1368-
logi("Attempt: " + (attempt + 1));
1369-
if (attempt < 2) {
1370-
sendLogBroadcast(LOG_LEVEL_WARNING, "Retrying...");
1381+
final int maxAttempts = 3;
1382+
if (attempt < maxAttempts - 1) {
1383+
logi("Retrying... (attempt " + (attempt + 2) + " / " + maxAttempts + ")");
1384+
sendLogBroadcast(LOG_LEVEL_WARNING, "Retrying... (attempt " + (attempt + 2) + " / " + maxAttempts + ")");
13711385

13721386
if (mConnectionState != STATE_DISCONNECTED) {
13731387
// Disconnect from the device
@@ -1387,6 +1401,23 @@ protected void onHandleIntent(@Nullable final Intent intent) {
13871401
else
13881402
startService(newIntent);
13891403
return;
1404+
} else {
1405+
final int error = mError & ~ERROR_CONNECTION_STATE_MASK;
1406+
if (error == 133 && after < before + 2000) {
1407+
// The DFU bootloader from SDK 8-11 is using Direct advertising when switched
1408+
// to DFU mode using Buttonless service. Some tested devices (e.g. Samsung Tab A8
1409+
// with Android 14) are not able to connect to such devices. The connection fails
1410+
// with error 133 around 1600 ms after calling connectGatt.
1411+
// Possible workarounds:
1412+
// 1. Bonding - reconnecting to devices advertising directly seems to work
1413+
// when the device is bonded. Try bonding (in app mode) before starting DFU.
1414+
// 2. Use button to switch to DFU mode - instead of using automatic Buttonless
1415+
// service, check if the device can be switched to bootloader mode using
1416+
// a button. In that case the device will advertise using broadcast.
1417+
logw("Hint: If all connection attempts quickly failed with error 133 " +
1418+
"the Android device may not support connecting to directly advertising " +
1419+
"devices without bonding. Bond, or use a button to switch to DFU mode.");
1420+
}
13901421
}
13911422
terminateConnection(gatt, mError);
13921423
return;
@@ -1451,7 +1482,7 @@ protected void onHandleIntent(@Nullable final Intent intent) {
14511482
startService(newIntent);
14521483
return;
14531484
}
1454-
report(ERROR_DEVICE_DISCONNECTED);
1485+
report(e.getErrorNumber());
14551486
} catch (final DfuException e) {
14561487
int error = e.getErrorNumber();
14571488
// Connection state errors and other Bluetooth GATT callbacks share the same error numbers. Therefore we are using bit masks to identify the type.
@@ -1948,12 +1979,12 @@ private void startForeground() {
19481979
updateForegroundNotification(builder);
19491980

19501981
try {
1951-
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
1952-
startForeground(NOTIFICATION_ID, builder.build(), ServiceInfo.FOREGROUND_SERVICE_TYPE_CONNECTED_DEVICE);
1953-
} else {
1982+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
1983+
startForeground(NOTIFICATION_ID, builder.build(), ServiceInfo.FOREGROUND_SERVICE_TYPE_CONNECTED_DEVICE);
1984+
} else {
19541985
startForeground(NOTIFICATION_ID, builder.build());
19551986
}
1956-
} catch (final SecurityException e) {
1987+
} catch (final SecurityException e) {
19571988
loge("Service cannot be started in foreground", e);
19581989
logi("Starting DFU service in background instead");
19591990
}

lib/dfu/src/main/java/no/nordicsemi/android/dfu/DfuCallback.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@
2727
/* package */ interface DfuCallback extends DfuController {
2828

2929
class DfuGattCallback extends BluetoothGattCallback {
30-
public void onDisconnected() {
30+
public void onDisconnected(int error) {
3131
// empty initial implementation
3232
}
3333
}

lib/dfu/src/main/java/no/nordicsemi/android/dfu/LegacyButtonlessDfuImpl.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -207,7 +207,7 @@ private int readVersion(@NonNull final BluetoothGatt gatt,
207207
@Nullable final BluetoothGattCharacteristic characteristic)
208208
throws DeviceDisconnectedException, DfuException, UploadAbortedException {
209209
if (!mConnected)
210-
throw new DeviceDisconnectedException("Unable to read version number: device disconnected");
210+
throw new DeviceDisconnectedException("Unable to read version number: device disconnected", mError);
211211
if (mAborted)
212212
throw new UploadAbortedException();
213213
// If the DFU Version characteristic is not available we return 0.
@@ -234,7 +234,7 @@ private int readVersion(@NonNull final BluetoothGatt gatt,
234234
loge("Sleeping interrupted", e);
235235
}
236236
if (!mConnected)
237-
throw new DeviceDisconnectedException("Unable to read version number: device disconnected");
237+
throw new DeviceDisconnectedException("Unable to read version number: device disconnected", mError);
238238
if (mError != 0)
239239
throw new DfuException("Unable to read version number", mError);
240240

lib/dfu/src/main/java/no/nordicsemi/android/dfu/LegacyDfuImpl.java

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,8 @@ public void onCharacteristicChanged(@NonNull final BluetoothGatt gatt,
111111
handlePacketReceiptNotification(gatt, characteristic, value);
112112
} else {
113113
/*
114-
* If the DFU target device is in invalid state (f.e. the Init Packet is required but has not been selected), the target will send DFU_STATUS_INVALID_STATE error
114+
* If the DFU target device is in invalid state (e.g. the Init Packet is required
115+
* but has not been selected), the target will send DFU_STATUS_INVALID_STATE error
115116
* for each firmware packet that was send. We are interested may ignore all but the first one.
116117
* After obtaining a remote DFU error the OP_CODE_RESET_KEY will be sent.
117118
*/
@@ -393,6 +394,18 @@ public void performDfu(@NonNull final Intent intent)
393394
}
394395
}
395396

397+
// Request short connection interval.
398+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
399+
logi("Requesting high connection priority");
400+
mService.sendLogBroadcast(DfuBaseService.LOG_LEVEL_VERBOSE,
401+
"Requesting high connection priority...");
402+
mService.sendLogBroadcast(DfuBaseService.LOG_LEVEL_DEBUG,
403+
"gatt.requestConnectionPriority(HIGH)");
404+
mGatt.requestConnectionPriority(BluetoothGatt.CONNECTION_PRIORITY_HIGH);
405+
// There will be a (hidden) callback on newer Android versions,
406+
// but we don't have to wait for it.
407+
}
408+
396409
/*
397410
* If the DFU Version characteristic is present and the version returned from it is greater or equal to 0.5, the Extended Init Packet is required.
398411
* For older versions, or if the DFU Version characteristic is not present (pre SDK 7.0.0), the Init Packet (which could have contained only the firmware CRC) was optional.
@@ -481,6 +494,10 @@ public void performDfu(@NonNull final Intent intent)
481494
status = getStatusCode(response, OP_CODE_RECEIVE_FIRMWARE_IMAGE_KEY);
482495
logi("Response received (Op Code = " + response[0] + ", Req Op Code = " + response[1] + ", Status = " + response[2] + ")");
483496
mService.sendLogBroadcast(DfuBaseService.LOG_LEVEL_APPLICATION, "Response received (Op Code = " + response[1] + ", Status = " + status + ")");
497+
if (status == 6 && numberOfPacketsBeforeNotification == 0 || numberOfPacketsBeforeNotification > 10) {
498+
logw("Hint: Error 6 (OPERATION FAILED) means the date were sent too fast for the target to handle. " +
499+
"Reduce the number of packets before notification (PRN) to 10 or less.");
500+
}
484501
if (status != DFU_STATUS_SUCCESS)
485502
throw new RemoteDfuException("Device returned error after sending file", status);
486503

@@ -665,7 +682,7 @@ private void writeImageSize(@NonNull final BluetoothGattCharacteristic character
665682
if (mAborted)
666683
throw new UploadAbortedException();
667684
if (!mConnected)
668-
throw new DeviceDisconnectedException("Unable to write Image Size: device disconnected");
685+
throw new DeviceDisconnectedException("Unable to write Image Size: device disconnected", mError);
669686
if (mError != 0)
670687
throw new DfuException("Unable to write Image Size", mError);
671688
}
@@ -726,7 +743,7 @@ private void writeImageSize(@NonNull final BluetoothGattCharacteristic character
726743
if (mAborted)
727744
throw new UploadAbortedException();
728745
if (!mConnected)
729-
throw new DeviceDisconnectedException("Unable to write Image Sizes: device disconnected");
746+
throw new DeviceDisconnectedException("Unable to write Image Sizes: device disconnected", mError);
730747
if (mError != 0)
731748
throw new DfuException("Unable to write Image Sizes", mError);
732749
}

0 commit comments

Comments
 (0)