Skip to content

Commit

Permalink
Added request to change connection priority
Browse files Browse the repository at this point in the history
Summary: #111

Reviewers: pawel.urban, dariusz.seweryn

Reviewed By: dariusz.seweryn

Differential Revision: https://phabricator.polidea.com/D2363
  • Loading branch information
mikolak committed May 29, 2017
1 parent fe670d2 commit c53c831
Show file tree
Hide file tree
Showing 9 changed files with 233 additions and 1 deletion.
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@

import android.bluetooth.BluetoothGattCharacteristic;
import android.bluetooth.BluetoothGattDescriptor;
import android.os.Build;
import android.support.annotation.NonNull;
import android.support.annotation.RequiresApi;

import com.polidea.rxandroidble.NotificationSetupMode;
import com.polidea.rxandroidble.RxBleConnection;
Expand Down Expand Up @@ -61,6 +63,11 @@ public RxBleConnectionMock(RxBleDeviceServices rxBleDeviceServices,
this.characteristicNotificationSources = characteristicNotificationSources;
}

@Override
public Completable requestConnectionPriority(int connectionPriority, long delay, @NonNull TimeUnit timeUnit) {
return Completable.complete();
}

@Override
public Observable<Integer> requestMtu(final int mtu) {
return Observable.fromCallable(new Callable<Integer>() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
import android.bluetooth.BluetoothGattCharacteristic;
import android.bluetooth.BluetoothGattDescriptor;
import android.os.Build;
import android.support.annotation.IntDef;
import android.support.annotation.IntRange;
import android.support.annotation.NonNull;
import android.support.annotation.RequiresApi;

Expand All @@ -15,9 +17,12 @@
import com.polidea.rxandroidble.exceptions.BleGattOperationType;
import com.polidea.rxandroidble.internal.connection.RxBleGattCallback;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

import rx.Completable;
import rx.Observable;
import rx.Scheduler;

Expand All @@ -41,6 +46,16 @@ public interface RxBleConnection {
*/
int GATT_READ_MTU_OVERHEAD = 1;

/**
* Description of correct values of connection priority
*/
@Retention(RetentionPolicy.SOURCE)
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
@IntDef({BluetoothGatt.CONNECTION_PRIORITY_LOW_POWER,
BluetoothGatt.CONNECTION_PRIORITY_BALANCED,
BluetoothGatt.CONNECTION_PRIORITY_HIGH})
@interface ConnectionPriority { }

interface Connector {

Observable<RxBleConnection> prepareConnection(boolean autoConnect);
Expand Down Expand Up @@ -411,6 +426,46 @@ Observable<Observable<byte[]>> setupIndication(@NonNull BluetoothGattCharacteris
*/
Observable<byte[]> writeDescriptor(BluetoothGattDescriptor descriptor, byte[] data);


/**
* Performs a GATT request connection priority operation, which requests a connection parameter
* update on the remote device. NOTE: peripheral may silently decline request.
* <p>
* Tells Android to request an update of connection interval and slave latency parameters.
* Using {@link BluetoothGatt#CONNECTION_PRIORITY_HIGH} will increase transmission speed and
* battery drainage, if accepted by the device, compared to {@link BluetoothGatt#CONNECTION_PRIORITY_BALANCED},
* while using {@link BluetoothGatt#CONNECTION_PRIORITY_LOW_POWER} will cause higher latencies
* and save battery, if accepted by the device, compared to {@link BluetoothGatt#CONNECTION_PRIORITY_BALANCED}.
* <p>
* By default connection is balanced.
* <p>
* NOTE: Due to lack of support for `BluetoothGattCallback.onConnectionPriorityChanged()` or similar it
* is not possible to know if the request was successful (accepted by the peripheral). This also causes
* the need of specifying when the request is considered finished (parameter delay and timeUnit).
* <p>
* As of Lollipop the connection parameters are:
* * {@link BluetoothGatt#CONNECTION_PRIORITY_BALANCED}: min interval 30 ms, max interval 50 ms, slave latency 0
* * {@link BluetoothGatt#CONNECTION_PRIORITY_HIGH}: min interval 7.5 ms, max interval 10ms, slave latency 0
* * {@link BluetoothGatt#CONNECTION_PRIORITY_LOW_POWER}: min interval 100ms, max interval 125 ms, slave latency 2
* <p>
* Returned completable completes after the specified delay if and only if
* {@link BluetoothGatt#requestConnectionPriority(int)} has returned true.
*
* @param connectionPriority requested connection priority
* @param delay delay after which operation is assumed to be successful (must be shorter than 30 seconds)
* @param timeUnit time unit of the delay
* @return Completable which finishes after calling the request and the specified delay
* @throws BleGattCannotStartException with {@link BleGattOperationType#CONNECTION_PRIORITY_CHANGE} type
* if requested operation returned false or threw exception
* @throws IllegalArgumentException in case of invalid connection priority or delay
*/
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
Completable requestConnectionPriority(
@ConnectionPriority int connectionPriority,
@IntRange(from = 1) long delay,
@NonNull TimeUnit timeUnit
);

/**
* Performs GATT read rssi operation.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
import rx.functions.Func1;

/**
* Service discovery result containing list of services and characteristics withing the services.
* Service discovery result containing list of services and characteristics within the services.
*/
public class RxBleDeviceServices {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ public class BleGattOperationType {
public static final BleGattOperationType RELIABLE_WRITE_COMPLETED = new BleGattOperationType("RELIABLE_WRITE_COMPLETED");
public static final BleGattOperationType READ_RSSI = new BleGattOperationType("READ_RSSI");
public static final BleGattOperationType ON_MTU_CHANGED = new BleGattOperationType("ON_MTU_CHANGED");
public static final BleGattOperationType CONNECTION_PRIORITY_CHANGE = new BleGattOperationType("CONNECTION_PRIORITY_CHANGE");
private final String description;

private BleGattOperationType(String description) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
import javax.inject.Named;
import javax.inject.Provider;

import rx.Completable;
import rx.Observable;
import rx.Scheduler;
import rx.functions.Action1;
Expand Down Expand Up @@ -75,6 +76,29 @@ public LongWriteOperationBuilder createNewLongWriteBuilder() {
return longWriteOperationBuilderProvider.get();
}

@Override
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
public Completable requestConnectionPriority(int connectionPriority, long delay, @NonNull TimeUnit timeUnit) {
if (connectionPriority != BluetoothGatt.CONNECTION_PRIORITY_LOW_POWER
&& connectionPriority != BluetoothGatt.CONNECTION_PRIORITY_BALANCED
&& connectionPriority != BluetoothGatt.CONNECTION_PRIORITY_HIGH) {
return Completable.error(
new IllegalArgumentException(
"Connection priority must have valid value from BluetoothGatt (received "
+ connectionPriority + ")"
)
);
}

if (delay <= 0) {
return Completable.error(new IllegalArgumentException("Delay must be bigger than 0"));
}

return rxBleRadio
.queue(operationsProvider.provideConnectionPriorityChangeOperation(connectionPriority, delay, timeUnit))
.toCompletable();
}

@Override
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
public Observable<Integer> requestMtu(int mtu) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,4 +32,10 @@ RxBleRadioOperationCharacteristicLongWrite provideLongWriteOperation(
RxBleRadioOperationCharacteristicWrite provideWriteCharacteristic(BluetoothGattCharacteristic characteristic, byte[] data);

RxBleRadioOperationDescriptorWrite provideWriteDescriptor(BluetoothGattDescriptor bluetoothGattDescriptor, byte[] data);

RxBleRadioOperationConnectionPriorityRequest provideConnectionPriorityChangeOperation(
int connectionPriority,
long delay,
TimeUnit timeUnit
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -99,4 +99,13 @@ public RxBleRadioOperationDescriptorWrite provideWriteDescriptor(BluetoothGattDe
return new RxBleRadioOperationDescriptorWrite(rxBleGattCallback, bluetoothGatt, timeoutConfiguration,
BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT, bluetoothGattDescriptor, data);
}

@Override
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
public RxBleRadioOperationConnectionPriorityRequest provideConnectionPriorityChangeOperation(int connectionPriority,
long delay,
TimeUnit timeUnit) {
return new RxBleRadioOperationConnectionPriorityRequest(rxBleGattCallback, bluetoothGatt, timeoutConfiguration,
connectionPriority, delay, timeUnit, timeoutScheduler);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package com.polidea.rxandroidble.internal.operations;

import android.bluetooth.BluetoothGatt;
import android.os.Build;
import android.support.annotation.RequiresApi;

import com.polidea.rxandroidble.exceptions.BleGattCannotStartException;
import com.polidea.rxandroidble.exceptions.BleGattOperationType;
import com.polidea.rxandroidble.internal.RxBleSingleGattRadioOperation;
import com.polidea.rxandroidble.internal.connection.RxBleGattCallback;

import java.util.concurrent.TimeUnit;

import javax.inject.Inject;

import rx.Observable;
import rx.Scheduler;

public class RxBleRadioOperationConnectionPriorityRequest extends RxBleSingleGattRadioOperation<Long> {

private final int connectionPriority;
private final long operationTimeout;
private final TimeUnit timeUnit;
private final Scheduler delayScheduler;

@Inject
RxBleRadioOperationConnectionPriorityRequest(
RxBleGattCallback rxBleGattCallback,
BluetoothGatt bluetoothGatt,
TimeoutConfiguration timeoutConfiguration,
int connectionPriority,
long operationTimeout,
TimeUnit timeUnit,
Scheduler delayScheduler) {
super(bluetoothGatt, rxBleGattCallback, BleGattOperationType.CONNECTION_PRIORITY_CHANGE, timeoutConfiguration);
this.connectionPriority = connectionPriority;
this.operationTimeout = operationTimeout;
this.timeUnit = timeUnit;
this.delayScheduler = delayScheduler;
}

@Override
protected Observable<Long> getCallback(RxBleGattCallback rxBleGattCallback) {
return Observable.timer(operationTimeout, timeUnit, delayScheduler);
}

@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
@Override
protected boolean startOperation(BluetoothGatt bluetoothGatt) throws IllegalArgumentException, BleGattCannotStartException {
return bluetoothGatt.requestConnectionPriority(connectionPriority);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
package com.polidea.rxandroidble.internal.operations

import android.bluetooth.BluetoothGatt
import com.polidea.rxandroidble.exceptions.BleGattCannotStartException
import com.polidea.rxandroidble.internal.connection.RxBleGattCallback
import com.polidea.rxandroidble.internal.util.MockOperationTimeoutConfiguration
import rx.observers.TestSubscriber
import rx.schedulers.TestScheduler
import spock.lang.Specification

import java.util.concurrent.Semaphore
import java.util.concurrent.TimeUnit

class RxBleRadioOperationConnectionPriorityRequestTest extends Specification {

static long timeout = 10
int completedDelay = 500L
TimeUnit delayUnit = TimeUnit.MILLISECONDS
BluetoothGatt mockBluetoothGatt = Mock BluetoothGatt
RxBleGattCallback mockGattCallback = Mock RxBleGattCallback
Semaphore mockSemaphore = Mock Semaphore
TestSubscriber<Integer> testSubscriber = new TestSubscriber()
TestScheduler testScheduler = new TestScheduler()
TimeoutConfiguration mockTimeoutConfiguration = new MockOperationTimeoutConfiguration(
timeout.intValue(),
testScheduler
)
RxBleRadioOperationConnectionPriorityRequest objectUnderTest

def setup() {
prepareObjectUnderTest(BluetoothGatt.CONNECTION_PRIORITY_LOW_POWER)
}

def "should call BluetoothGatt.requestConnectionPriority(int) exactly once when run()"() {
when:
objectUnderTest.run()

then:
1 * mockBluetoothGatt.requestConnectionPriority(_) >> true
}

def "should complete after specified time if BluetoothGatt.requestConnectionPriority() will return true"() {
given:
mockBluetoothGatt.requestConnectionPriority(_) >> true
objectUnderTest.run()

when:
testScheduler.advanceTimeBy(completedDelay + 500, delayUnit)

then:
testSubscriber.assertCompleted()
}

def "should throw exception if operation failed"() {
given:
mockBluetoothGatt.requestConnectionPriority(_) >> false

when:
objectUnderTest.run()

then:
testSubscriber.assertError BleGattCannotStartException
}

def prepareObjectUnderTest(int connectionPriority) {
objectUnderTest = new RxBleRadioOperationConnectionPriorityRequest(
mockGattCallback,
mockBluetoothGatt,
mockTimeoutConfiguration,
connectionPriority,
completedDelay,
delayUnit,
testScheduler
)
objectUnderTest.setRadioBlockingSemaphore(mockSemaphore)
objectUnderTest.asObservable().subscribe(testSubscriber)
}
}

0 comments on commit c53c831

Please sign in to comment.