Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 30 additions & 26 deletions ble/src/main/java/no/nordicsemi/android/ble/BleManagerHandler.java
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,11 @@ abstract class BleManagerHandler extends RequestHandler {
* Current connection parameters. Those values are only available starting from Android Oreo.
*/
private int interval, latency, timeout;
/**
* Samsung S8 with Android 9 fails to reconnect to devices requesting PHY LE 2M just after
* connection. Workaround would be to disable PHY LE 2M on the device side.
*/
private boolean earlyPhyLe2MRequest;
/**
* Last received battery value or -1 if value wasn't received.
*
Expand Down Expand Up @@ -753,6 +758,7 @@ private boolean internalConnect(@NonNull final BluetoothDevice device,
postConnectionStateChange(o -> o.onDeviceConnecting(device));
}
connectionTime = SystemClock.elapsedRealtime();
earlyPhyLe2MRequest = false;
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.O) {
// connectRequest will never be null here.
final int preferredPhy = connectRequest.getPreferredPhy();
Expand Down Expand Up @@ -2301,7 +2307,10 @@ public void onConnectionStateChange(@NonNull final BluetoothGatt gatt,
// ...because the next method sets them to false.

// notifyDeviceDisconnected(...) may call close()
if (timeout) {

if (status == GattError.GATT_CONN_TIMEOUT && earlyPhyLe2MRequest) {
notifyDeviceDisconnected(gatt.getDevice(), ConnectionObserver.REASON_UNSUPPORTED_CONFIGURATION);
} else if (timeout) {
notifyDeviceDisconnected(gatt.getDevice(), ConnectionObserver.REASON_TIMEOUT);
} else if (notSupported) {
notifyDeviceDisconnected(gatt.getDevice(), ConnectionObserver.REASON_NOT_SUPPORTED);
Expand Down Expand Up @@ -2332,7 +2341,9 @@ public void onConnectionStateChange(@NonNull final BluetoothGatt gatt,
}
if (cr != null) {
int reason;
if (notSupported)
if (status == GattError.GATT_CONN_TIMEOUT && earlyPhyLe2MRequest)
reason = FailCallback.REASON_UNSUPPORTED_CONFIGURATION;
else if (notSupported)
reason = FailCallback.REASON_DEVICE_NOT_SUPPORTED;
else if (status == BluetoothGatt.GATT_SUCCESS)
reason = FailCallback.REASON_DEVICE_DISCONNECTED;
Expand Down Expand Up @@ -2524,13 +2535,9 @@ public void onCharacteristicRead(@NonNull final BluetoothGatt gatt,
rr.notifySuccess(gatt.getDevice());
}
}
} else if (status == 137 /* GATT AUTH FAIL */) {
// Bonding failed or was cancelled.
Log.w(TAG, "Reading failed with status " + status);
// The bond state receiver will fail the request. Stop here.
return;
} else if (status == BluetoothGatt.GATT_INSUFFICIENT_AUTHENTICATION
|| status == 8 /* GATT INSUF AUTHORIZATION */) {
|| status == 8 /* GATT INSUF AUTHORIZATION */
|| status == 137 /* GATT AUTH FAIL */) {
// This is called when bonding attempt failed, but the app is still trying to read.
// We need to cancel the request here, as bonding won't start.
log(Log.WARN, () -> "Authentication required (" + status + ")");
Expand Down Expand Up @@ -2578,13 +2585,9 @@ public void onCharacteristicWrite(final BluetoothGatt gatt,
wr.notifySuccess(gatt.getDevice());
}
}
} else if (status == 137 /* GATT AUTH FAIL */) {
// This never happens for Write operations, for some reason.
Log.w(TAG, "Writing failed with status " + status);
// The bond state receiver will fail the request. Stop here.
return;
} else if (status == BluetoothGatt.GATT_INSUFFICIENT_AUTHENTICATION
|| status == 8 /* GATT INSUF AUTHORIZATION */) {
|| status == 8 /* GATT INSUF AUTHORIZATION */
|| status == 137 /* GATT AUTH FAIL */) {
// This is called when bonding attempt failed, but the app is still trying to write.
// We need to cancel the request here, as bonding won't start.
log(Log.WARN, () -> "Authentication required (" + status + ")");
Expand All @@ -2595,6 +2598,9 @@ public void onCharacteristicWrite(final BluetoothGatt gatt,
}
if (request instanceof final WriteRequest wr) {
wr.notifyFail(gatt.getDevice(), status);
// Automatically abort Reliable Write when write error happen
if (requestQueue instanceof final ReliableWriteRequest rwr)
rwr.notifyAndCancelQueue(gatt.getDevice());
}
} else {
Log.e(TAG, "onCharacteristicWrite error " + status + ", bond state: " + gatt.getDevice().getBondState());
Expand Down Expand Up @@ -2658,13 +2664,9 @@ public void onDescriptorRead(final @NonNull BluetoothGatt gatt,
rr.notifySuccess(gatt.getDevice());
}
}
} else if (status == 137 /* GATT AUTH FAIL */) {
// Bonding failed or was cancelled.
Log.w(TAG, "Reading descriptor failed with status " + status);
// The bond state receiver will fail the request. Stop here.
return;
} else if (status == BluetoothGatt.GATT_INSUFFICIENT_AUTHENTICATION
|| status == 8 /* GATT INSUF AUTHORIZATION */) {
|| status == 8 /* GATT INSUF AUTHORIZATION */
|| status == 137 /* GATT AUTH FAIL */) {
// This is called when bonding attempt failed, but the app is still trying to read.
// We need to cancel the request here, as bonding won't start.
log(Log.WARN, () -> "Authentication required (" + status + ")");
Expand Down Expand Up @@ -2722,13 +2724,9 @@ public void onDescriptorWrite(final BluetoothGatt gatt,
wr.notifySuccess(gatt.getDevice());
}
}
} else if (status == 137 /* GATT AUTH FAIL */) {
// This never happens for Write operations, for some reason.
Log.w(TAG, "Writing descriptor failed with status " + status);
// The bond state receiver will fail the request. Stop here.
return;
} else if (status == BluetoothGatt.GATT_INSUFFICIENT_AUTHENTICATION
|| status == 8 /* GATT INSUF AUTHORIZATION */) {
|| status == 8 /* GATT INSUF AUTHORIZATION */
|| status == 137 /* GATT AUTH FAIL */) {
// This is called when bonding attempt failed, but the app is still trying to write.
// We need to cancel the request here, as bonding won't start.
log(Log.WARN, () -> "Authentication required (" + status + ")");
Expand All @@ -2739,6 +2737,9 @@ public void onDescriptorWrite(final BluetoothGatt gatt,
}
if (request instanceof final WriteRequest wr) {
wr.notifyFail(gatt.getDevice(), status);
// Automatically abort Reliable Write when write error happen
if (requestQueue instanceof final ReliableWriteRequest rwr)
rwr.notifyAndCancelQueue(gatt.getDevice());
}
} else {
Log.e(TAG, "onDescriptorWrite error " + status + ", bond state: " + gatt.getDevice().getBondState());
Expand Down Expand Up @@ -2958,6 +2959,9 @@ public void onPhyUpdate(@NonNull final BluetoothGatt gatt,
log(Log.INFO, () ->
"PHY updated (TX: " + ParserUtils.phyToString(txPhy) +
", RX: " + ParserUtils.phyToString(rxPhy) + ")");
// Samsung S8 fails to reconnect when PHY LE 2M request is sent before service discovery.
earlyPhyLe2MRequest = earlyPhyLe2MRequest ||
(txPhy == BluetoothDevice.PHY_LE_2M && !servicesDiscovered);
if (request instanceof final PhyRequest pr) {
pr.notifyPhyChanged(gatt.getDevice(), txPhy, rxPhy);
pr.notifySuccess(gatt.getDevice());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,19 +23,37 @@
package no.nordicsemi.android.ble.callback;

import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothGatt;
import no.nordicsemi.android.ble.BleManager;

import androidx.annotation.NonNull;

@FunctionalInterface
public interface FailCallback {
int REASON_DEVICE_DISCONNECTED = -1;
/**
* Returned when the {@link BleManager#isRequiredServiceSupported(BluetoothGatt)}
* returns false, that is when at least required GATT service was not discovered
* on the connected device.
*/
int REASON_DEVICE_NOT_SUPPORTED = -2;
int REASON_NULL_ATTRIBUTE = -3;
int REASON_REQUEST_FAILED = -4;
int REASON_TIMEOUT = -5;
int REASON_VALIDATION = -6;
int REASON_CANCELLED = -7;
int REASON_NOT_ENABLED = -8;
/**
* The Android device is unable to reconnect to the peripheral because of internal failure.
* Most probably it cannot respond properly to PHY LE 2M update procedure, causing the
* remote device to terminate the connection.
* <p>
* Try disabling PHY LE 2M on the peripheral side, or update the Android version.
* If that's not possible, the connection to your device may not work on the given
* Android device at all. If the device is bonded, try removing bonding and connect,
* but this seems to fix the problem only before a new bond is created.
*/
int REASON_UNSUPPORTED_CONFIGURATION = -9;
int REASON_BLUETOOTH_DISABLED = -100;

/**
Expand All @@ -47,7 +65,8 @@ public interface FailCallback {
* {@link #REASON_DEVICE_DISCONNECTED}, {@link #REASON_TIMEOUT},
* {@link #REASON_DEVICE_NOT_SUPPORTED} (only for Connect request),
* {@link #REASON_BLUETOOTH_DISABLED}, {@link #REASON_NULL_ATTRIBUTE},
* {@link #REASON_VALIDATION}, {@link #REASON_CANCELLED}, {@link #REASON_NOT_ENABLED}
* {@link #REASON_VALIDATION}, {@link #REASON_CANCELLED}, {@link #REASON_NOT_ENABLED},
* {@link #REASON_UNSUPPORTED_CONFIGURATION},
* or {@link #REASON_REQUEST_FAILED} (for other reason).
*/
void onRequestFailed(@NonNull final BluetoothDevice device, final int status);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ public static String parseConnectionError(final int error) {
return switch (error) {
case GATT_SUCCESS -> "SUCCESS";
case GATT_CONN_L2C_FAILURE -> "GATT CONN L2C FAILURE";
case GATT_INSUF_AUTHENTICATION -> "GATT INSUF AUTHENTICATION";
case GATT_CONN_TIMEOUT -> "GATT CONN TIMEOUT";
case GATT_CONN_TERMINATE_PEER_USER -> "GATT CONN TERMINATE PEER USER";
case GATT_CONN_TERMINATE_LOCAL_HOST -> "GATT CONN TERMINATE LOCAL HOST";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ public interface ConnectionObserver {
* Android will try to reconnect automatically.
*/
int REASON_LINK_LOSS = 3;
/** The device does not hav required services. */
/** The device does not have required services. */
int REASON_NOT_SUPPORTED = 4;
/** Connection attempt was cancelled. */
int REASON_CANCELLED = 5;
Expand All @@ -58,6 +58,17 @@ public interface ConnectionObserver {
* or doesn't respond for another reason.
*/
int REASON_TIMEOUT = 10;
/**
* The Android device is unable to reconnect to the peripheral because of internal failure.
* Most probably it cannot respond properly to PHY LE 2M update procedure, causing the
* remote device to terminate the connection.
* <p>
* Try disabling PHY LE 2M on the peripheral side, or update the Android version.
* If that's not possible, the connection to your device may not work on the given
* Android device at all. If the device is bonded, try removing bonding and connect,
* but this seems to fix the problem only before a new bond is created.
*/
int REASON_UNSUPPORTED_CONFIGURATION = 11;

/**
* Called when the Android device started connecting to given device.
Expand Down