Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 4bb9810b authored by Qiao Yang's avatar Qiao Yang Committed by Gerrit Code Review
Browse files

Merge "BumbleBluetoothTests: add LeAdvertisingTest instrumentation test"

parents 10e396c9 0fed2ae8
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -68,6 +68,7 @@ java_sdk_library {
        "//external/sl4a/Common",
        "//frameworks/opt/wear",
        "//packages/modules/Bluetooth/android/app/tests/unit",
        "//packages/modules/Bluetooth/framework/tests/bumble",
        "//packages/modules/Bluetooth/android/pandora/server",
        "//packages/modules/Bluetooth/service",
        "//packages/modules/Connectivity/nearby/tests/multidevices/clients/test_support/fastpair_provider",
+33 −0
Original line number Diff line number Diff line
android_test {
    name: "BumbleBluetoothTests",
    defaults: ["framework-bluetooth-tests-defaults"],

    min_sdk_version: "current",
    target_sdk_version: "current",
    libs: [
        "android.test.base",
        "android.test.runner",
        "libprotobuf-java-micro",
    ],

    static_libs: [
        "androidx.test.ext.truth",
        "androidx.test.rules",
        "androidx.core_core",
        "grpc-java-lite",
        "grpc-java-okhttp-client-lite",
        "opencensus-java-contrib-grpc-metrics",
        "pandora_experimental-grpc-java",
        "pandora_experimental-proto-java",
    ],

    // Include all test java files.
    srcs: ["src/**/*.java"],

    data:[":bumble_pandora_server"],

    platform_apis: true,
    test_suites: [
        "device-tests",
    ],
}
+18 −0
Original line number Diff line number Diff line
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="android.bluetooth" >

    <uses-sdk android:minSdkVersion="21" android:targetSdkVersion="21" />

    <uses-permission android:name="android.permission.BLUETOOTH" />
    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.LOCAL_MAC_ADDRESS" />

    <application>
        <uses-library android:name="android.test.runner" />
    </application>

    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
                     android:targetPackage="android.bluetooth"
                     android:label="Bumble Bluetooth Test"/>

</manifest>
+57 −0
Original line number Diff line number Diff line
<?xml version="1.0" encoding="utf-8"?>
<!--
  ~ Copyright (C) 2020 The Android Open Source Project
  ~
  ~ Licensed under the Apache License, Version 2.0 (the "License");
  ~ you may not use this file except in compliance with the License.
  ~ You may obtain a copy of the License at
  ~
  ~      http://www.apache.org/licenses/LICENSE-2.0
  ~
  ~ Unless required by applicable law or agreed to in writing, software
  ~ distributed under the License is distributed on an "AS IS" BASIS,
  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  ~ See the License for the specific language governing permissions and
  ~ limitations under the License.
  -->
<configuration description="Bumble bluetooth tests.">
    <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
        <option name="cleanup-apks" value="true" />
        <option name="test-file-name" value="BumbleBluetoothTests.apk" />
        <option name="install-arg" value="-g" />
    </target_preparer>

    <target_preparer class="com.android.tradefed.targetprep.PythonVirtualenvPreparer">
        <option name="dep-module" value="grpcio==1.51.1" />
        <option name="dep-module" value="cryptography==35" />
    </target_preparer>

    <target_preparer class="com.android.tradefed.targetprep.RunHostCommandTargetPreparer">
      <option name="python-virtualenv" value="true"/>
      <option name="host-setup-command" value="adb -s $SERIAL reverse tcp:7999 tcp:7999" />
      <option name="host-background-command"
        value="$EXTRA_FILE(target_testcases)/BumbleBluetoothTests/bumble_pandora_server"/>
      <option name="host-teardown-command" value="adb -s $SERIAL reverse --remove tcp:7999" />
    </target_preparer>

    <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
        <option name="throw-if-cmd-fail" value="true" />
        <option name="run-command" value="cmd bluetooth_manager disable" />
        <option name="run-command" value="cmd bluetooth_manager wait-for-state:STATE_OFF" />
        <option name="run-command" value="cmd bluetooth_manager enable" />
        <option name="run-command" value="cmd bluetooth_manager wait-for-state:STATE_ON" />
    </target_preparer>

    <option name="test-tag" value="BumbleBluetoothTests" />
    <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
        <option name="package" value="android.bluetooth" />
    </test>

    <!-- Only run if the Bluetooth Mainline module is installed. -->
    <object type="module_controller"
            class="com.android.tradefed.testtype.suite.module.MainlineTestModuleController">
        <option name="mainline-module-package-name" value="com.android.btservices" />
        <option name="mainline-module-package-name" value="com.google.android.btservices" />
    </object>
</configuration>
+196 −0
Original line number Diff line number Diff line
package android.bluetooth;

import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothManager;
import android.bluetooth.Utils;
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.filters.SmallTest;
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.okhttp.OkHttpChannelBuilder;
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 LOG_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 {
        // FactorReset is killing the server and restart
        // all channel created before the server restarted
        // cannot be reused
        ManagedChannel channel = OkHttpChannelBuilder
              .forAddress("localhost", 7999)
              .usePlaintext()
              .build();

        HostGrpc.HostBlockingStub stub = HostGrpc.newBlockingStub(channel);
        stub.factoryReset(Empty.getDefaultInstance());

        // terminate the channel
        channel.shutdown().awaitTermination(1, TimeUnit.SECONDS);

        // Create a new channel for all successive grpc calls
        mChannel = OkHttpChannelBuilder
              .forAddress("localhost", 7999)
              .usePlaintext()
              .build();

        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(LOG_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(LOG_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(LOG_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(LOG_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(LOG_TAG,"scan observer: scan response address: " + addr);

                if (addr.equals(address)) {
                    future.complete(response);
                }
            }

            @Override
            public void onError(Throwable e) {
                Log.e(LOG_TAG,"scan observer: on error " + e);
                future.completeExceptionally(e);
            }

            @Override
            public void onCompleted() {
                Log.i(LOG_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);
        });
    }
}
Loading