-
Notifications
You must be signed in to change notification settings - Fork 581
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Added Bluetooth Adapter state monitoring to connection (#275)
`DeadObjectException` is usually thrown when interacting with `BluetoothAdapter` or `BluetoothGatt` instance that was obtained before bluetooth being turned off. Prior to library version `1.3.0` all errors raised when interacting with `BluetoothGatt` or recieved by `BluetoothGattCallback` were closing the connection (were emitted by `RxBleDevice.establishConnection()`). After `1.3.0` only errors raised in `RxBleRadioOperationConnect` and recieved by `BluetoothGattCallback.onConnectionStateChange()` were closing the connection so it was possible that `DeadObjectException`s raised repetedly by subscribing to i.e. `RxBleConnection.readRssi()` would not close the connection. Added monitoring of BluetoothAdapter’s state to prevent `DeadObjectException`s and inform the user about the connection loss as soon as possible.
- Loading branch information
1 parent
53d85dc
commit d44afab
Showing
4 changed files
with
231 additions
and
179 deletions.
There are no files selected for viewing
74 changes: 74 additions & 0 deletions
74
...idble/src/main/java/com/polidea/rxandroidble/internal/connection/DisconnectionRouter.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,74 @@ | ||
package com.polidea.rxandroidble.internal.connection; | ||
|
||
|
||
import com.jakewharton.rxrelay.BehaviorRelay; | ||
import com.polidea.rxandroidble.RxBleAdapterStateObservable; | ||
import com.polidea.rxandroidble.exceptions.BleDisconnectedException; | ||
import com.polidea.rxandroidble.exceptions.BleException; | ||
import com.polidea.rxandroidble.internal.DeviceModule; | ||
import javax.inject.Inject; | ||
import javax.inject.Named; | ||
import rx.Observable; | ||
import rx.functions.Func1; | ||
|
||
/** | ||
* A class that is responsible for routing all potential sources of disconnection to an Observable that emits only errors. | ||
*/ | ||
@ConnectionScope | ||
class DisconnectionRouter { | ||
|
||
private final BehaviorRelay<BleException> disconnectionErrorRelay = BehaviorRelay.create(); | ||
|
||
private final Observable disconnectionErrorObservable; | ||
|
||
@Inject | ||
DisconnectionRouter( | ||
@Named(DeviceModule.MAC_ADDRESS) final String macAddress, | ||
Observable<RxBleAdapterStateObservable.BleAdapterState> adapterStateObservable | ||
) { | ||
disconnectionErrorObservable = Observable.merge( | ||
disconnectionErrorRelay | ||
.flatMap(new Func1<BleException, Observable<?>>() { | ||
@Override | ||
public Observable<?> call(BleException e) { | ||
return Observable.error(e); | ||
} | ||
}), | ||
adapterStateObservable | ||
.filter(new Func1<RxBleAdapterStateObservable.BleAdapterState, Boolean>() { | ||
@Override | ||
public Boolean call(RxBleAdapterStateObservable.BleAdapterState bleAdapterState) { | ||
return !bleAdapterState.isUsable(); | ||
} | ||
}) | ||
.flatMap(new Func1<RxBleAdapterStateObservable.BleAdapterState, Observable<?>>() { | ||
@Override | ||
public Observable<?> call(RxBleAdapterStateObservable.BleAdapterState bleAdapterState) { | ||
return Observable.error(new BleDisconnectedException(macAddress)); // TODO: Introduce BleDisabledException? | ||
} | ||
}) | ||
) | ||
.replay() | ||
.autoConnect(0); | ||
} | ||
|
||
/** | ||
* Method to be called whenever a connection braking exception happens. It will be routed to {@link #asObservable()}. | ||
* | ||
* @param bleException the exception that happened | ||
*/ | ||
void route(BleException bleException) { | ||
disconnectionErrorRelay.call(bleException); | ||
} | ||
|
||
/** | ||
* Function returning an Observable that will only throw error in case of a disconnection | ||
* | ||
* @param <T> the type of returned observable | ||
* @return the Observable | ||
*/ | ||
<T> Observable<T> asObservable() { | ||
//noinspection unchecked | ||
return disconnectionErrorObservable; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
91 changes: 91 additions & 0 deletions
91
...c/test/groovy/com/polidea/rxandroidble/internal/connection/DisconnectionRouterTest.groovy
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,91 @@ | ||
package com.polidea.rxandroidble.internal.connection | ||
|
||
import static com.polidea.rxandroidble.RxBleAdapterStateObservable.BleAdapterState.STATE_OFF | ||
import static com.polidea.rxandroidble.RxBleAdapterStateObservable.BleAdapterState.STATE_TURNING_OFF | ||
import static com.polidea.rxandroidble.RxBleAdapterStateObservable.BleAdapterState.STATE_TURNING_ON | ||
|
||
import com.polidea.rxandroidble.RxBleAdapterStateObservable | ||
import com.polidea.rxandroidble.exceptions.BleDisconnectedException | ||
import com.polidea.rxandroidble.exceptions.BleException | ||
import org.robospock.RoboSpecification | ||
import rx.observers.TestSubscriber | ||
import rx.subjects.PublishSubject | ||
import spock.lang.Unroll | ||
|
||
class DisconnectionRouterTest extends RoboSpecification { | ||
|
||
String mockMacAddress = "1234" | ||
PublishSubject<RxBleAdapterStateObservable.BleAdapterState> mockAdapterStateSubject = PublishSubject.create() | ||
DisconnectionRouter objectUnderTest = new DisconnectionRouter(mockMacAddress, mockAdapterStateSubject) | ||
TestSubscriber testSubscriber = new TestSubscriber() | ||
|
||
def "should emit exception from .asObservable() when got one from .route()"() { | ||
|
||
given: | ||
BleException testException = new BleException() | ||
objectUnderTest.asObservable().subscribe(testSubscriber) | ||
|
||
when: | ||
objectUnderTest.route(testException) | ||
|
||
then: | ||
testSubscriber.assertError(testException) | ||
} | ||
|
||
def "should emit exception from .asObservable() when got one from .route() even before subscription"() { | ||
|
||
given: | ||
BleException testException = new BleException() | ||
objectUnderTest.route(testException) | ||
|
||
when: | ||
objectUnderTest.asObservable().subscribe(testSubscriber) | ||
|
||
then: | ||
testSubscriber.assertError(testException) | ||
} | ||
|
||
@Unroll | ||
def "should emit exception from .asObservable() when adapterStateObservable emits STATE_TURNING_ON/STATE_TURNING_OFF/STATE_OFF"() { | ||
|
||
given: | ||
objectUnderTest.asObservable().subscribe(testSubscriber) | ||
|
||
when: | ||
mockAdapterStateSubject.onNext(bleAdapterState) | ||
|
||
then: | ||
testSubscriber.assertError({ BleDisconnectedException e -> e.bluetoothDeviceAddress == mockMacAddress }) | ||
|
||
where: | ||
bleAdapterState << [ STATE_TURNING_ON, STATE_TURNING_OFF, STATE_OFF ] | ||
} | ||
|
||
@Unroll | ||
def "should emit exception from .asObservable() when adapterStateObservable emits STATE_TURNING_ON/STATE_TURNING_OFF/STATE_OFF even before subscription"() { | ||
|
||
given: | ||
mockAdapterStateSubject.onNext(bleAdapterState) | ||
|
||
when: | ||
objectUnderTest.asObservable().subscribe(testSubscriber) | ||
|
||
then: | ||
testSubscriber.assertError({ BleDisconnectedException e -> e.bluetoothDeviceAddress == mockMacAddress }) | ||
|
||
where: | ||
bleAdapterState << [ STATE_TURNING_ON, STATE_TURNING_OFF, STATE_OFF ] | ||
} | ||
|
||
def "should not emit exception from .asObservable() when adapterStateObservable emits STATE_ON"() { | ||
|
||
given: | ||
objectUnderTest.asObservable().subscribe(testSubscriber) | ||
|
||
when: | ||
mockAdapterStateSubject.onNext(RxBleAdapterStateObservable.BleAdapterState.STATE_ON) | ||
|
||
then: | ||
testSubscriber.assertNoErrors() | ||
} | ||
} |
Oops, something went wrong.