blob: ea904c37894071c0fd008758922bc345d7fa94cf [file] [log] [blame]
package android.bluetooth;
import static android.bluetooth.Utils.factoryResetAndCreateNewChannel;
import android.bluetooth.le.AdvertiseData;
import android.bluetooth.le.AdvertisingSet;
import android.bluetooth.le.AdvertisingSetCallback;
import android.bluetooth.le.AdvertisingSetParameters;
import android.bluetooth.le.BluetoothLeAdvertiser;
import android.util.Log;
import androidx.core.util.Pair;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.platform.app.InstrumentationRegistry;
import androidx.test.runner.AndroidJUnit4;
import static com.google.common.truth.Truth.assertThat;
import com.google.protobuf.Empty;
import io.grpc.Context.CancellableContext;
import io.grpc.Deadline;
import io.grpc.ManagedChannel;
import io.grpc.stub.StreamObserver;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
import org.junit.After;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import pandora.HostGrpc;
import pandora.HostProto.ScanRequest;
import pandora.HostProto.ScanningResponse;
/**
* Test cases for {@link AdvertiseManager}.
*/
@RunWith(AndroidJUnit4.class)
public class LeAdvertisingTest {
private static final String TAG = "LeAdvertisingTest";
private static final int TIMEOUT_ADVERTISING_MS = 1000;
private static ManagedChannel mChannel;
private static HostGrpc.HostBlockingStub mHostBlockingStub;
private static HostGrpc.HostStub mHostStub;
@BeforeClass
public static void setUpClass() throws Exception {
InstrumentationRegistry.getInstrumentation().getUiAutomation()
.adoptShellPermissionIdentity();
}
@Before
public void setUp() throws Exception {
// Cleanup previous channels and create a new channel for all successive grpc calls
mChannel = factoryResetAndCreateNewChannel();
mHostBlockingStub = HostGrpc.newBlockingStub(mChannel);
mHostStub = HostGrpc.newStub(mChannel);
mHostBlockingStub.withWaitForReady().readLocalAddress(Empty.getDefaultInstance());
}
@After
public void tearDown() throws Exception {
// terminate the channel
mChannel.shutdown().awaitTermination(1, TimeUnit.SECONDS);
}
@Test
public void advertisingSet() throws Exception {
ScanningResponse response = startAdvertising()
.thenCompose(advAddressPair -> scanWithBumble(advAddressPair))
.join();
Log.i(TAG, "scan response: " + response);
assertThat(response).isNotNull();
}
private CompletableFuture<Pair<String, Integer>> startAdvertising() {
CompletableFuture<Pair<String, Integer>> future =
new CompletableFuture<Pair<String, Integer>>();
android.content.Context context = ApplicationProvider.getApplicationContext();
BluetoothManager bluetoothManager = context.getSystemService(BluetoothManager.class);
BluetoothAdapter bluetoothAdapter = bluetoothManager.getAdapter();
// Start advertising
BluetoothLeAdvertiser leAdvertiser = bluetoothAdapter.getBluetoothLeAdvertiser();
AdvertisingSetParameters parameters = new AdvertisingSetParameters.Builder().
setOwnAddressType(AdvertisingSetParameters.ADDRESS_TYPE_RANDOM).build();
AdvertiseData advertiseData = new AdvertiseData.Builder().build();
AdvertiseData scanResponse = new AdvertiseData.Builder().build();
AdvertisingSetCallback advertisingSetCallback =
new AdvertisingSetCallback() {
@Override
public void onAdvertisingSetStarted(
AdvertisingSet advertisingSet, int txPower, int status) {
Log.i(
TAG,
"onAdvertisingSetStarted "
+ " txPower:"
+ txPower
+ " status:"
+ status);
advertisingSet.enableAdvertising(true, TIMEOUT_ADVERTISING_MS, 0);
}
@Override
public void onOwnAddressRead(
AdvertisingSet advertisingSet, int addressType, String address) {
Log.i(
TAG,
"onOwnAddressRead "
+ " addressType:"
+ addressType
+ " address:"
+ address);
future.complete(new Pair<String, Integer>(address, addressType));
}
@Override
public void onAdvertisingEnabled(
AdvertisingSet advertisingSet, boolean enabled, int status) {
Log.i(
TAG,
"onAdvertisingEnabled "
+ " enabled:"
+ enabled
+ " status:"
+ status);
advertisingSet.getOwnAddress();
}
};
leAdvertiser.startAdvertisingSet(parameters, advertiseData, scanResponse,
null, null, 0, 0, advertisingSetCallback);
return future;
}
private CompletableFuture<ScanningResponse> scanWithBumble(Pair<String, Integer> addressPair) {
final CompletableFuture<ScanningResponse> future =
new CompletableFuture<ScanningResponse>();
CancellableContext withCancellation = io.grpc.Context.current().withCancellation();
String address = addressPair.first;
int addressType = addressPair.second;
ScanRequest request = ScanRequest.newBuilder().build();
StreamObserver<ScanningResponse> responseObserver =
new StreamObserver<ScanningResponse>() {
public void onNext(ScanningResponse response) {
String addr = "";
if (addressType == AdvertisingSetParameters.ADDRESS_TYPE_PUBLIC) {
addr = Utils.addressStringFromByteString(response.getPublic());
} else {
addr = Utils.addressStringFromByteString(response.getRandom());
}
Log.i(TAG, "scan observer: scan response address: " + addr);
if (addr.equals(address)) {
future.complete(response);
}
}
@Override
public void onError(Throwable e) {
Log.e(TAG, "scan observer: on error " + e);
future.completeExceptionally(e);
}
@Override
public void onCompleted() {
Log.i(TAG, "scan observer: on completed");
future.complete(null);
}
};
Deadline initialDeadline = Deadline.after(TIMEOUT_ADVERTISING_MS, TimeUnit.MILLISECONDS);
withCancellation.run(() -> mHostStub.withDeadline(initialDeadline)
.scan(request, responseObserver));
return future.whenComplete((input, exception) -> {
withCancellation.cancel(null);
});
}
}