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

Commit f0a9f31f authored by Jack He's avatar Jack He
Browse files

BumbleBluetoothTests: Add PairingTest Using Bumble

* Add a pairing test to verify that a BR/EDR pairing can
  be established between Android and Bumble
* Use mockito IntentMatchers to check for incoming intent

Test: atest BumbleBluetoothTests
Bug: 300052980
Change-Id: Ia7ef28477f852f1f89eb1f9ab3369e9e4059546f
parent 56516df6
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",
+194 −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 com.google.common.truth.Truth.assertThat;

import static org.hamcrest.Matchers.allOf;
import static org.mockito.hamcrest.MockitoHamcrest.argThat;
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.IntentFilter;

import androidx.test.espresso.intent.matcher.IntentMatchers;
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.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 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();
        mInOrder.verify(mReceiver, timeout(BOND_INTENT_TIMEOUT.toMillis()))
                .onReceive(
                        any(Context.class),
                        argThat(
                                allOf(
                                        IntentMatchers.hasAction(
                                                BluetoothDevice.ACTION_BOND_STATE_CHANGED),
                                        IntentMatchers.hasExtra(
                                                BluetoothDevice.EXTRA_DEVICE, mBumbleDevice),
                                        IntentMatchers.hasExtra(
                                                BluetoothDevice.EXTRA_BOND_STATE,
                                                BluetoothDevice.BOND_BONDING))));

        mInOrder.verify(mReceiver, timeout(BOND_INTENT_TIMEOUT.toMillis()))
                .onReceive(
                        any(Context.class),
                        argThat(
                                allOf(
                                        IntentMatchers.hasAction(
                                                BluetoothDevice.ACTION_PAIRING_REQUEST),
                                        IntentMatchers.hasExtra(
                                                BluetoothDevice.EXTRA_DEVICE, mBumbleDevice),
                                        IntentMatchers.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());

        mInOrder.verify(mReceiver, timeout(BOND_INTENT_TIMEOUT.toMillis()))
                .onReceive(
                        any(Context.class),
                        argThat(
                                allOf(
                                        IntentMatchers.hasAction(
                                                BluetoothDevice.ACTION_BOND_STATE_CHANGED),
                                        IntentMatchers.hasExtra(
                                                BluetoothDevice.EXTRA_DEVICE, mBumbleDevice),
                                        IntentMatchers.hasExtra(
                                                BluetoothDevice.EXTRA_BOND_STATE,
                                                BluetoothDevice.BOND_BONDED))));

        verifyNoMoreInteractions(mReceiver);
    }

    private void removeBond(BluetoothDevice device) {
        assertThat(device.removeBond()).isTrue();
        mInOrder.verify(mReceiver, timeout(BOND_INTENT_TIMEOUT.toMillis()))
                .onReceive(
                        any(Context.class),
                        argThat(
                                allOf(
                                        IntentMatchers.hasAction(
                                                BluetoothDevice.ACTION_BOND_STATE_CHANGED),
                                        IntentMatchers.hasExtra(
                                                BluetoothDevice.EXTRA_DEVICE, device),
                                        IntentMatchers.hasExtra(
                                                BluetoothDevice.EXTRA_BOND_STATE,
                                                BluetoothDevice.BOND_NONE))));
        verifyNoMoreInteractions(mReceiver);
    }
}
+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);
    }
}