1 package android.bluetooth; 2 3 import android.bluetooth.BluetoothAdapter; 4 import android.bluetooth.BluetoothManager; 5 import android.bluetooth.Utils; 6 import android.bluetooth.le.AdvertiseData; 7 import android.bluetooth.le.AdvertisingSet; 8 import android.bluetooth.le.AdvertisingSetCallback; 9 import android.bluetooth.le.AdvertisingSetParameters; 10 import android.bluetooth.le.BluetoothLeAdvertiser; 11 import android.util.Log; 12 13 import androidx.core.util.Pair; 14 import androidx.test.core.app.ApplicationProvider; 15 import androidx.test.filters.SmallTest; 16 import androidx.test.platform.app.InstrumentationRegistry; 17 import androidx.test.runner.AndroidJUnit4; 18 19 import static com.google.common.truth.Truth.assertThat; 20 import com.google.protobuf.Empty; 21 22 import io.grpc.Context.CancellableContext; 23 import io.grpc.Deadline; 24 import io.grpc.ManagedChannel; 25 import io.grpc.okhttp.OkHttpChannelBuilder; 26 import io.grpc.stub.StreamObserver; 27 28 import java.util.concurrent.CompletableFuture; 29 import java.util.concurrent.TimeUnit; 30 31 import org.junit.After; 32 import org.junit.Before; 33 import org.junit.BeforeClass; 34 import org.junit.Test; 35 import org.junit.runner.RunWith; 36 37 import pandora.HostGrpc; 38 import pandora.HostProto.ScanRequest; 39 import pandora.HostProto.ScanningResponse; 40 41 42 /** 43 * Test cases for {@link AdvertiseManager}. 44 */ 45 @RunWith(AndroidJUnit4.class) 46 public class LeAdvertisingTest { 47 48 private static final String LOG_TAG = "LeAdvertisingTest"; 49 50 private static final int TIMEOUT_ADVERTISING_MS = 1000; 51 52 private static ManagedChannel mChannel; 53 54 private static HostGrpc.HostBlockingStub mHostBlockingStub; 55 56 private static HostGrpc.HostStub mHostStub; 57 58 @BeforeClass setUpClass()59 public static void setUpClass() throws Exception { 60 InstrumentationRegistry.getInstrumentation().getUiAutomation() 61 .adoptShellPermissionIdentity(); 62 } 63 64 @Before setUp()65 public void setUp() throws Exception { 66 // FactorReset is killing the server and restart 67 // all channel created before the server restarted 68 // cannot be reused 69 ManagedChannel channel = OkHttpChannelBuilder 70 .forAddress("localhost", 7999) 71 .usePlaintext() 72 .build(); 73 74 HostGrpc.HostBlockingStub stub = HostGrpc.newBlockingStub(channel); 75 stub.factoryReset(Empty.getDefaultInstance()); 76 77 // terminate the channel 78 channel.shutdown().awaitTermination(1, TimeUnit.SECONDS); 79 80 // Create a new channel for all successive grpc calls 81 mChannel = OkHttpChannelBuilder 82 .forAddress("localhost", 7999) 83 .usePlaintext() 84 .build(); 85 86 mHostBlockingStub = HostGrpc.newBlockingStub(mChannel); 87 mHostStub = HostGrpc.newStub(mChannel); 88 mHostBlockingStub.withWaitForReady().readLocalAddress(Empty.getDefaultInstance()); 89 } 90 91 @After tearDown()92 public void tearDown() throws Exception { 93 // terminate the channel 94 mChannel.shutdown().awaitTermination(1, TimeUnit.SECONDS); 95 } 96 97 @Test advertisingSet()98 public void advertisingSet() throws Exception { 99 ScanningResponse response = startAdvertising() 100 .thenCompose(advAddressPair -> scanWithBumble(advAddressPair)) 101 .join(); 102 103 Log.i(LOG_TAG, "scan response: " + response); 104 assertThat(response).isNotNull(); 105 } 106 startAdvertising()107 private CompletableFuture<Pair<String, Integer>> startAdvertising() { 108 CompletableFuture<Pair<String, Integer>> future = 109 new CompletableFuture<Pair<String, Integer>>(); 110 111 android.content.Context context = ApplicationProvider.getApplicationContext(); 112 BluetoothManager bluetoothManager = context.getSystemService(BluetoothManager.class); 113 BluetoothAdapter bluetoothAdapter = bluetoothManager.getAdapter(); 114 115 // Start advertising 116 BluetoothLeAdvertiser leAdvertiser = bluetoothAdapter.getBluetoothLeAdvertiser(); 117 AdvertisingSetParameters parameters = new AdvertisingSetParameters.Builder(). 118 setOwnAddressType(AdvertisingSetParameters.ADDRESS_TYPE_RANDOM).build(); 119 AdvertiseData advertiseData = new AdvertiseData.Builder().build(); 120 AdvertiseData scanResponse = new AdvertiseData.Builder().build(); 121 AdvertisingSetCallback advertisingSetCallback = new AdvertisingSetCallback() { 122 @Override 123 public void onAdvertisingSetStarted(AdvertisingSet advertisingSet, int txPower, 124 int status) { 125 Log.i(LOG_TAG, "onAdvertisingSetStarted " + " txPower:" + txPower 126 + " status:" + status); 127 advertisingSet.enableAdvertising(true, TIMEOUT_ADVERTISING_MS, 0); 128 } 129 @Override 130 public void onOwnAddressRead(AdvertisingSet advertisingSet, int addressType, 131 String address) { 132 Log.i(LOG_TAG, "onOwnAddressRead " + " addressType:" + addressType 133 + " address:" + address); 134 future.complete(new Pair<String, Integer>(address, addressType)); 135 } 136 @Override 137 public void onAdvertisingEnabled(AdvertisingSet advertisingSet, boolean enabled, 138 int status) { 139 Log.i(LOG_TAG, "onAdvertisingEnabled " + " enabled:" + enabled 140 + " status:" + status); 141 advertisingSet.getOwnAddress(); 142 } 143 }; 144 leAdvertiser.startAdvertisingSet(parameters, advertiseData, scanResponse, 145 null, null, 0, 0, advertisingSetCallback); 146 147 return future; 148 } 149 scanWithBumble(Pair<String, Integer> addressPair)150 private CompletableFuture<ScanningResponse> scanWithBumble(Pair<String, Integer> addressPair) { 151 final CompletableFuture<ScanningResponse> future = 152 new CompletableFuture<ScanningResponse>(); 153 CancellableContext withCancellation = io.grpc.Context.current().withCancellation(); 154 155 String address = addressPair.first; 156 int addressType = addressPair.second; 157 158 ScanRequest request = ScanRequest.newBuilder().build(); 159 StreamObserver<ScanningResponse> responseObserver = new StreamObserver<ScanningResponse>(){ 160 public void onNext(ScanningResponse response) { 161 String addr = ""; 162 if (addressType == AdvertisingSetParameters.ADDRESS_TYPE_PUBLIC) { 163 addr = Utils.addressStringFromByteString(response.getPublic()); 164 } 165 else { 166 addr = Utils.addressStringFromByteString(response.getRandom()); 167 } 168 Log.i(LOG_TAG,"scan observer: scan response address: " + addr); 169 170 if (addr.equals(address)) { 171 future.complete(response); 172 } 173 } 174 175 @Override 176 public void onError(Throwable e) { 177 Log.e(LOG_TAG,"scan observer: on error " + e); 178 future.completeExceptionally(e); 179 } 180 181 @Override 182 public void onCompleted() { 183 Log.i(LOG_TAG,"scan observer: on completed"); 184 future.complete(null); 185 } 186 }; 187 188 Deadline initialDeadline = Deadline.after(TIMEOUT_ADVERTISING_MS, TimeUnit.MILLISECONDS); 189 withCancellation.run(() -> mHostStub.withDeadline(initialDeadline) 190 .scan(request, responseObserver)); 191 192 return future.whenComplete((input, exception) -> { 193 withCancellation.cancel(null); 194 }); 195 } 196 } 197