Skip to content

Commit

Permalink
Added GATT status code to BleDisconnectException (#405) (#456)
Browse files Browse the repository at this point in the history
* Added GATT status code to BleDisconnectException (#405)

* Added suggested RestrictTo LIBRARY_GROUP annotation fo adapterDisabled utility function.
  • Loading branch information
Cierpliwy authored and dariuszseweryn committed Jul 6, 2018
1 parent 2fe8164 commit 094e9a4
Show file tree
Hide file tree
Showing 14 changed files with 188 additions and 20 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package com.polidea.rxandroidble.exceptions;

public class BleAdapterDisabledException extends BleException {
// Disconnection related to disabled Bluetooth adapter
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,36 +2,77 @@

import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.annotation.RestrictTo;

import com.polidea.rxandroidble.utils.GattStatusParser;

/**
* Exception emitted when the BLE link has been disconnected either when the connection was already established
* or was in pending connection state. This state is expected when the connection was released as a
* part of expected behavior (with {@link android.bluetooth.BluetoothGatt#GATT_SUCCESS} state).
*
* @see com.polidea.rxandroidble.RxBleDevice#establishConnection(Context, boolean)
* @see com.polidea.rxandroidble.RxBleDevice#establishConnection(boolean)
*/
public class BleDisconnectedException extends BleException {

/**
* Set when the state is not available, for example when the adapter has been switched off.
*/
public static final int UNKNOWN_STATUS = -1;

@SuppressWarnings("WeakerAccess")
@NonNull
public final String bluetoothDeviceAddress;
public final int state;

@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
public static BleDisconnectedException adapterDisabled(String macAddress) {
return new BleDisconnectedException(new BleAdapterDisabledException(), macAddress, UNKNOWN_STATUS);
}

/**
* @deprecated In general, there's no place in a public API that requires you to instantiate this exception directly.
* If you use it anyway, please switch to {@link #BleDisconnectedException(String, int)}
*/
@Deprecated
public BleDisconnectedException() {
bluetoothDeviceAddress = "";
this("", UNKNOWN_STATUS);
}

/**
* @deprecated In general, there's no place in a public API that requires you to instantiate this exception directly.
* If you use it anyway, please switch to {@link #BleDisconnectedException(Throwable, String, int)}
*/
@Deprecated
public BleDisconnectedException(Throwable throwable, @NonNull String bluetoothDeviceAddress) {
super(createMessage(bluetoothDeviceAddress), throwable);
this.bluetoothDeviceAddress = bluetoothDeviceAddress;
this(throwable, bluetoothDeviceAddress, UNKNOWN_STATUS);
}

/**
* @deprecated In general, there's no place in a public API that requires you to instantiate this exception directly.
* If you use it anyway, please switch to {@link #BleDisconnectedException(String, int)} or don't use it.
*/
@Deprecated
public BleDisconnectedException(@NonNull String bluetoothDeviceAddress) {
super(createMessage(bluetoothDeviceAddress));
this(bluetoothDeviceAddress, UNKNOWN_STATUS);
}

@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
public BleDisconnectedException(Throwable throwable, @NonNull String bluetoothDeviceAddress, int status) {
super(createMessage(bluetoothDeviceAddress, status), throwable);
this.bluetoothDeviceAddress = bluetoothDeviceAddress;
this.state = status;
}

@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
public BleDisconnectedException(@NonNull String bluetoothDeviceAddress, int status) {
super(createMessage(bluetoothDeviceAddress, status));
this.bluetoothDeviceAddress = bluetoothDeviceAddress;
this.state = status;
}

private static String createMessage(@Nullable String bluetoothDeviceAddress) {
return "Disconnected from " + bluetoothDeviceAddress;
private static String createMessage(@Nullable String bluetoothDeviceAddress, int status) {
final String gattCallbackStatusDescription = GattStatusParser.getGattCallbackStatusDescription(status);
return "Disconnected from " + bluetoothDeviceAddress + " with status " + status + " (" + gattCallbackStatusDescription + ")";
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;

import com.polidea.rxandroidble.utils.GattStatusParser;

/**
* Exception emitted when the BLE link has been interrupted as a result of an error. The exception contains
* detailed explanation of the error source (type of operation) and the code proxied from
Expand Down Expand Up @@ -65,9 +67,10 @@ private static String createMessage(@Nullable BluetoothGatt gatt, int status, Bl
getMacAddress(gatt), bleGattOperationType);
}

final String statusDescription = GattStatusParser.getGattCallbackStatusDescription(status);
final String link
= "https://android.googlesource.com/platform/external/bluetooth/bluedroid/+/android-5.1.0_r1/stack/include/gatt_api.h";
return String.format("GATT exception from MAC address %s, status %d, type %s. (Look up status 0x%02x here %s)",
getMacAddress(gatt), status, bleGattOperationType, status, link);
return String.format("GATT exception from MAC address %s, status %d (%s), type %s. (Look up status 0x%02x here %s)",
getMacAddress(gatt), status, statusDescription, bleGattOperationType, status, link);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,17 @@

import com.polidea.rxandroidble.internal.serialization.QueueReleaseInterface;
import com.polidea.rxandroidble.internal.util.QueueReleasingEmitterWrapper;

import java.util.concurrent.TimeUnit;

import rx.Emitter;
import rx.Observable;
import rx.Scheduler;
import rx.Subscription;

/**
* A convenience class intended to use with {@link BluetoothGatt} functions that fire one-time actions.
*
* @param <T> The type of emitted result.
*/
public abstract class SingleResponseOperation<T> extends QueueOperation<T> {
Expand Down Expand Up @@ -74,6 +77,7 @@ final protected void protectedRun(final Emitter<T> emitter, final QueueReleaseIn

/**
* A function that should call the passed {@link BluetoothGatt} and return `true` if the call has succeeded.
*
* @param bluetoothGatt the {@link BluetoothGatt} to use
* @return `true` if success, `false` otherwise
*/
Expand All @@ -89,6 +93,7 @@ protected Observable<T> timeoutFallbackProcedure(

@Override
protected BleException provideException(DeadObjectException deadObjectException) {
return new BleDisconnectedException(deadObjectException, bluetoothGatt.getDevice().getAddress());
return new BleDisconnectedException(deadObjectException, bluetoothGatt.getDevice().getAddress(),
BleDisconnectedException.UNKNOWN_STATUS);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import com.polidea.rxandroidble.exceptions.BleGattException;
import com.polidea.rxandroidble.internal.DeviceModule;
import com.polidea.rxandroidble.internal.util.RxBleAdapterWrapper;

import bleshadow.javax.inject.Inject;
import bleshadow.javax.inject.Named;
import rx.Observable;
Expand Down Expand Up @@ -45,7 +46,7 @@ public Boolean call(Boolean isAdapterUsable) {
.map(new Func1<Boolean, BleException>() {
@Override
public BleException call(Boolean isAdapterUsable) {
return new BleDisconnectedException(macAddress); // TODO: Introduce BleDisabledException?
return BleDisconnectedException.adapterDisabled(macAddress);
}
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -354,7 +354,8 @@ public void call() {

@Override
protected BleException provideException(DeadObjectException deadObjectException) {
return new BleDisconnectedException(deadObjectException, bluetoothGatt.getDevice().getAddress());
return new BleDisconnectedException(deadObjectException, bluetoothGatt.getDevice().getAddress(),
BleDisconnectedException.UNKNOWN_STATUS);
}
});
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState
bluetoothGattProvider.updateBluetoothGatt(gatt);

if (isDisconnectedOrDisconnecting(newState)) {
disconnectionRouter.onDisconnectedException(new BleDisconnectedException(gatt.getDevice().getAddress()));
disconnectionRouter.onDisconnectedException(new BleDisconnectedException(gatt.getDevice().getAddress(), status));
} else if (status != BluetoothGatt.GATT_SUCCESS) {
disconnectionRouter.onGattConnectionStateException(
new BleGattException(gatt, status, BleGattOperationType.CONNECTION_STATE)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,8 @@ public void call(Throwable throwable) {

@Override
protected BleException provideException(DeadObjectException deadObjectException) {
return new BleDisconnectedException(deadObjectException, bluetoothGatt.getDevice().getAddress());
return new BleDisconnectedException(deadObjectException, bluetoothGatt.getDevice().getAddress(),
BleDisconnectedException.UNKNOWN_STATUS);
}

@NonNull
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,6 @@ public void cancel() throws Exception {

@Override
protected BleException provideException(DeadObjectException deadObjectException) {
return new BleDisconnectedException(deadObjectException, bluetoothDevice.getAddress());
return new BleDisconnectedException(deadObjectException, bluetoothDevice.getAddress(), BleDisconnectedException.UNKNOWN_STATUS);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,6 @@ public void call() {

@Override
protected BleException provideException(DeadObjectException deadObjectException) {
return new BleDisconnectedException(deadObjectException, macAddress);
return new BleDisconnectedException(deadObjectException, macAddress, BleDisconnectedException.UNKNOWN_STATUS);
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.polidea.rxandroidble.internal.serialization;

import android.support.annotation.RestrictTo;

import com.polidea.rxandroidble.ClientComponent;
import com.polidea.rxandroidble.exceptions.BleDisconnectedException;
import com.polidea.rxandroidble.exceptions.BleException;
Expand All @@ -10,8 +11,10 @@
import com.polidea.rxandroidble.internal.connection.ConnectionSubscriptionWatcher;
import com.polidea.rxandroidble.internal.connection.DisconnectionRouterOutput;
import com.polidea.rxandroidble.internal.operations.Operation;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;

import bleshadow.javax.inject.Inject;
import bleshadow.javax.inject.Named;
import rx.Emitter;
Expand Down Expand Up @@ -144,6 +147,6 @@ public void call(BleException bleException) {
public void onConnectionUnsubscribed() {
disconnectionThrowableSubscription.unsubscribe();
disconnectionThrowableSubscription = null;
terminate(new BleDisconnectedException(deviceMacAddress));
terminate(new BleDisconnectedException(deviceMacAddress, BleDisconnectedException.UNKNOWN_STATUS));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
/**
* Parsing thanks to
* https://github.com/adafruit/Bluefruit_LE_Connect_Android/blob/
* master/app/src/main/java/com/adafruit/bluefruit/le/connect/ble/StandardUUIDs.java
* Bluefruit LE Connect for Android
* <p>
* <p>
* The MIT License (MIT)
* <p>
* Copyright (c) 2015 Adafruit Industries
* <p>
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
* <p>
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
* <p>
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/

package com.polidea.rxandroidble.utils;

import android.annotation.SuppressLint;

import java.util.Collections;
import java.util.HashMap;
import java.util.Map;

public class GattStatusParser {

private static final Map<Integer, String> GATT_STATUS;

static {
@SuppressLint("UseSparseArrays") Map<Integer, String> aMap = new HashMap<>();
aMap.put(0x00, "GATT_SUCCESS");
aMap.put(0x01, "GATT_INVALID_HANDLE");
aMap.put(0x02, "GATT_READ_NOT_PERMIT");
aMap.put(0x03, "GATT_WRITE_NOT_PERMIT");
aMap.put(0x04, "GATT_INVALID_PDU");
aMap.put(0x05, "GATT_INSUF_AUTHENTICATION");
aMap.put(0x06, "GATT_REQ_NOT_SUPPORTED");
aMap.put(0x07, "GATT_INVALID_OFFSET");
aMap.put(0x08, "GATT_INSUF_AUTHORIZATION");
aMap.put(0x09, "GATT_PREPARE_Q_FULL");
aMap.put(0x0a, "GATT_NOT_FOUND");
aMap.put(0x0b, "GATT_NOT_LONG");
aMap.put(0x0c, "GATT_INSUF_KEY_SIZE");
aMap.put(0x0d, "GATT_INVALID_ATTR_LEN");
aMap.put(0x0e, "GATT_ERR_UNLIKELY");
aMap.put(0x0f, "GATT_INSUF_ENCRYPTION");
aMap.put(0x10, "GATT_UNSUPPORT_GRP_TYPE");
aMap.put(0x11, "GATT_INSUF_RESOURCE");

aMap.put(0x87, "GATT_ILLEGAL_PARAMETER");
aMap.put(0x80, "GATT_NO_RESOURCES");
aMap.put(0x81, "GATT_INTERNAL_ERROR");
aMap.put(0x82, "GATT_WRONG_STATE");
aMap.put(0x83, "GATT_DB_FULL");
aMap.put(0x84, "GATT_BUSY");
aMap.put(0x85, "GATT_ERROR");
aMap.put(0x86, "GATT_CMD_STARTED");
aMap.put(0x88, "GATT_PENDING");
aMap.put(0x89, "GATT_AUTH_FAIL");
aMap.put(0x8a, "GATT_MORE");
aMap.put(0x8b, "GATT_INVALID_CFG");
aMap.put(0x8c, "GATT_SERVICE_STARTED");
aMap.put(0x00, "GATT_SUCCESS");
aMap.put(0x8d, "GATT_ENCRYPED_NO_MITM");
aMap.put(0x8e, "GATT_NOT_ENCRYPTED");
aMap.put(0x8f, "GATT_CONGESTED");

aMap.put(0xfd, "GATT_CCC_CFG_ERR");
aMap.put(0xfe, "GATT_PRC_IN_PROGRESS");
aMap.put(0xff, "GATT_OUT_OF_RANGE");
GATT_STATUS = Collections.unmodifiableMap(aMap);
}

private GattStatusParser() {
// utility class
}

public static String getGattCallbackStatusDescription(int status) {
final String description = GATT_STATUS.get(status);
return description == null ? "UNKNOWN" : description;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,25 @@ class BleDisconnectedExceptionTest extends Specification {

BleDisconnectedException objectUnderTest

def "toString should include message"() {
def "toString should include message with unknown status"() {

when:
objectUnderTest = new BleDisconnectedException("myBluetoothAddress")

then:
assert objectUnderTest.toString() ==
"com.polidea.rxandroidble.exceptions.BleDisconnectedException: Disconnected from myBluetoothAddress"
"com.polidea.rxandroidble.exceptions.BleDisconnectedException: Disconnected from myBluetoothAddress with status -1 (UNKNOWN)"
}

def "toString should include message with status"() {
given:
def expectedStatus = 0x81

when:
objectUnderTest = new BleDisconnectedException("myBluetoothAddress", expectedStatus)

then:
assert objectUnderTest.toString() ==
"com.polidea.rxandroidble.exceptions.BleDisconnectedException: Disconnected from myBluetoothAddress with status $expectedStatus (GATT_INTERNAL_ERROR)"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ class BleGattExceptionTest extends Specification {

then:
assert objectUnderTest.toString() ==
"com.polidea.rxandroidble.exceptions.BleGattException: GATT exception from MAC address null, status 10, " +
"com.polidea.rxandroidble.exceptions.BleGattException: GATT exception from MAC address null, status 10 (GATT_NOT_FOUND), " +
"type BleGattOperation{description='CONNECTION_STATE'}. " +
"(Look up status 0x0a here " +
"https://android.googlesource.com/platform/external/bluetooth/bluedroid/+/android-5.1.0_r1/stack/include/gatt_api.h)"
Expand Down

0 comments on commit 094e9a4

Please sign in to comment.