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

Commit 0c6a9510 authored by Treehugger Robot's avatar Treehugger Robot Committed by Gerrit Code Review
Browse files

Merge changes Id157a1f9,Ia7ef2847,I0385b44c,I0c6a4107 into main

* changes:
  Bumble PairingTest: Suggestion format
  BumbleBluetoothTests: Add PairingTest Using Bumble
  bt-test-utils-lib: Handle UiAutomation.ALL_PERMISSIONS
  BumbleBluetoothTests: Collect btsnoop and debug logcat logging
parents 0184e04d 7983f0f7
Loading
Loading
Loading
Loading
+2 −0
Original line number Diff line number Diff line
@@ -20,9 +20,11 @@ android_test_helper_app {

    static_libs: [
        "androidx.core_core",
        "androidx.test.espresso.intents",
        "androidx.test.ext.junit",
        "androidx.test.ext.truth",
        "androidx.test.rules",
        "bluetooth-test-util-lib",
        "compatibility-device-util-axt",
        "grpc-java-lite",
        "grpc-java-okhttp-client-lite",
+18 −0
Original line number Diff line number Diff line
<?xml version="1.0" encoding="utf-8"?>
  <configuration description="Bumble bluetooth tests.">
    <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer">
        <option name="force-root" value="true"/>
    </target_preparer>

    <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
        <option name="cleanup-apks" value="true" />
        <option name="test-file-name" value="BumbleBluetoothTestsApp.apk" />
@@ -26,6 +30,13 @@

    <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
        <option name="throw-if-cmd-fail" value="true" />
        <option name="run-command" value="setprop persist.bluetooth.btsnooplogmode full" />
        <option name="run-command" value="device_config set_sync_disabled_for_tests persistent" />
        <option name="run-command"
                value="device_config put bluetooth INIT_logging_debug_enabled_for_all true" />
        <option name="run-command"
                value="device_config put bluetooth INIT_default_log_level_str LOG_VERBOSE" />
        <option name="run-command" value="settings put global ble_scan_always_enabled 0" />
        <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" />
@@ -43,5 +54,12 @@
        <option name="mainline-module-package-name" value="com.android.btservices" />
        <option name="mainline-module-package-name" value="com.google.android.btservices" />
    </object>

    <!-- Collect Bluetooth snoop logs for each test run -->
    <metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector">
        <option name="directory-keys" value="/data/misc/bluetooth/logs" />
        <option name="collect-on-run-ended-only" value="false" />
        <option name="clean-up" value="false" />
    </metrics_collector>
</configuration>
+174 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2023 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.
 */

package android.bluetooth;

import static androidx.test.espresso.intent.matcher.IntentMatchers.hasAction;
import static androidx.test.espresso.intent.matcher.IntentMatchers.hasExtra;

import static com.google.common.truth.Truth.assertThat;

import static org.mockito.Mockito.any;
import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.timeout;
import static org.mockito.Mockito.verifyNoMoreInteractions;

import android.bluetooth.test_utils.EnableBluetoothRule;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;

import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.platform.app.InstrumentationRegistry;

import com.android.compatibility.common.util.AdoptShellPermissionsRule;

import io.grpc.stub.StreamObserver;

import org.hamcrest.Matcher;
import org.hamcrest.core.AllOf;
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InOrder;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.mockito.hamcrest.MockitoHamcrest;

import java.time.Duration;
import java.util.Set;
import java.util.concurrent.TimeUnit;

import pandora.SecurityProto.PairingEvent;
import pandora.SecurityProto.PairingEventAnswer;

@RunWith(AndroidJUnit4.class)
public class PairingTest {
    private static final Duration BOND_INTENT_TIMEOUT = Duration.ofSeconds(10);

    private static final Context sTargetContext =
            InstrumentationRegistry.getInstrumentation().getTargetContext();
    private static final BluetoothAdapter sAdapter =
            sTargetContext.getSystemService(BluetoothManager.class).getAdapter();

    @Rule public final AdoptShellPermissionsRule mPermissionRule = new AdoptShellPermissionsRule();

    @Rule public final PandoraDevice mBumble = new PandoraDevice();

    @Rule
    public final EnableBluetoothRule mEnableBluetoothRule =
            new EnableBluetoothRule(false /* enableTestMode */, true /* toggleBluetooth */);

    @Mock private BroadcastReceiver mReceiver;
    private InOrder mInOrder = null;
    private BluetoothDevice mBumbleDevice;

    private final StreamObserverSpliterator<PairingEvent> mPairingEventStreamObserver =
            new StreamObserverSpliterator<>();

    @Before
    public void setUp() throws Exception {
        MockitoAnnotations.initMocks(this);
        mInOrder = inOrder(mReceiver);

        IntentFilter filter = new IntentFilter();
        filter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
        filter.addAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED);
        filter.addAction(BluetoothDevice.ACTION_PAIRING_REQUEST);
        sTargetContext.registerReceiver(mReceiver, filter);

        mBumbleDevice = mBumble.getRemoteDevice();
        Set<BluetoothDevice> bondedDevices = sAdapter.getBondedDevices();
        if (bondedDevices.contains(mBumbleDevice)) {
            removeBond(mBumbleDevice);
        }
    }

    @After
    public void tearDown() throws Exception {
        Set<BluetoothDevice> bondedDevices = sAdapter.getBondedDevices();
        if (bondedDevices.contains(mBumbleDevice)) {
            removeBond(mBumbleDevice);
        }
        mBumbleDevice = null;
        sTargetContext.unregisterReceiver(mReceiver);
    }

    /**
     * Test a simple BR/EDR just works pairing flow in the follow steps:
     *
     * <ol>
     *   <li>1. Bumble resets, enables inquiry and page scan, and sets I/O cap to no display no
     *       input
     *   <li>2. Android connects to Bumble via its MAC address
     *   <li>3. Android tries to create bond, emitting bonding intent 4. Android confirms the
     *       pairing via pairing request intent
     *   <li>5. Bumble confirms the pairing internally (optional, added only for test confirmation)
     *   <li>6. Android verifies bonded intent
     * </ol>
     */
    @Test
    public void testBrEdrPairing_phoneInitiatedBrEdrInquiryOnlyJustWorks() {
        StreamObserver<PairingEventAnswer> pairingEventAnswerObserver =
                mBumble.security()
                        .withDeadlineAfter(BOND_INTENT_TIMEOUT.toMillis(), TimeUnit.MILLISECONDS)
                        .onPairing(mPairingEventStreamObserver);

        assertThat(mBumbleDevice.createBond()).isTrue();
        verifyIntentReceived(
                hasAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED),
                hasExtra(BluetoothDevice.EXTRA_DEVICE, mBumbleDevice),
                hasExtra(BluetoothDevice.EXTRA_BOND_STATE, BluetoothDevice.BOND_BONDING));

        verifyIntentReceived(
                hasAction(BluetoothDevice.ACTION_PAIRING_REQUEST),
                hasExtra(BluetoothDevice.EXTRA_DEVICE, mBumbleDevice),
                hasExtra(
                        BluetoothDevice.EXTRA_PAIRING_VARIANT,
                        BluetoothDevice.PAIRING_VARIANT_CONSENT));
        mBumbleDevice.setPairingConfirmation(true);

        PairingEvent pairingEvent = mPairingEventStreamObserver.iterator().next();
        assertThat(pairingEvent.hasJustWorks()).isTrue();
        pairingEventAnswerObserver.onNext(
                PairingEventAnswer.newBuilder().setEvent(pairingEvent).setConfirm(true).build());

        verifyIntentReceived(
                hasAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED),
                hasExtra(BluetoothDevice.EXTRA_DEVICE, mBumbleDevice),
                hasExtra(BluetoothDevice.EXTRA_BOND_STATE, BluetoothDevice.BOND_BONDED));

        verifyNoMoreInteractions(mReceiver);
    }

    private void removeBond(BluetoothDevice device) {
        assertThat(device.removeBond()).isTrue();
        verifyIntentReceived(
                hasAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED),
                hasExtra(BluetoothDevice.EXTRA_DEVICE, mBumbleDevice),
                hasExtra(BluetoothDevice.EXTRA_BOND_STATE, BluetoothDevice.BOND_NONE));
        verifyNoMoreInteractions(mReceiver);
    }

    @SafeVarargs
    private void verifyIntentReceived(Matcher<Intent>... matchers) {
        mInOrder.verify(mReceiver, timeout(BOND_INTENT_TIMEOUT.toMillis()))
                .onReceive(any(Context.class), MockitoHamcrest.argThat(AllOf.allOf(matchers)));
    }
}
+6 −0
Original line number Diff line number Diff line
@@ -32,6 +32,7 @@ import java.util.concurrent.TimeUnit;
import pandora.DckGrpc;
import pandora.HostGrpc;
import pandora.HostProto;
import pandora.SecurityGrpc;

public final class PandoraDevice extends ExternalResource {
    private static final String TAG = PandoraDevice.class.getSimpleName();
@@ -113,4 +114,9 @@ public final class PandoraDevice extends ExternalResource {
    public DckGrpc.DckBlockingStub dckBlocking() {
        return DckGrpc.newBlockingStub(mChannel);
    }

    /** Get Pandora Security service */
    public SecurityGrpc.SecurityStub security() {
        return SecurityGrpc.newStub(mChannel);
    }
}
+7 −0
Original line number Diff line number Diff line
@@ -19,6 +19,7 @@ package android.bluetooth.test_utils;
import static android.Manifest.permission.BLUETOOTH_CONNECT;
import static android.Manifest.permission.BLUETOOTH_PRIVILEGED;

import android.app.UiAutomation;
import android.bluetooth.BluetoothAdapter;
import android.content.BroadcastReceiver;
import android.content.Context;
@@ -227,6 +228,9 @@ public class BluetoothAdapterUtils {

        Set<String> permissionsAdopted = TestUtils.getAdoptedShellPermissions();
        String[] permissionArray = permissionsAdopted.toArray(String[]::new);
        if (UiAutomation.ALL_PERMISSIONS.equals(permissionsAdopted)) {
            permissionArray = null;
        }

        sBluetoothAdapterLock.lock();
        try {
@@ -267,6 +271,9 @@ public class BluetoothAdapterUtils {

        Set<String> permissionsAdopted = TestUtils.getAdoptedShellPermissions();
        String[] permissionArray = permissionsAdopted.toArray(String[]::new);
        if (UiAutomation.ALL_PERMISSIONS.equals(permissionsAdopted)) {
            permissionArray = null;
        }

        sBluetoothAdapterLock.lock();
        try {