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

Commit 785630cc authored by TreeHugger Robot's avatar TreeHugger Robot Committed by Automerger Merge Worker
Browse files

Merge "Set up unit test environment for BassClientService" into tm-dev am: 89f4da04

Original change: https://googleplex-android-review.googlesource.com/c/platform/packages/modules/Bluetooth/+/17903542



Change-Id: Idd231ad37f54068ecbd3dad5268df49ba85748b9
Ignore-AOSP-First: this is an automerge
Signed-off-by: default avatarAutomerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
parents 73f2811d 89f4da04
Loading
Loading
Loading
Loading
+11 −7
Original line number Original line Diff line number Diff line
@@ -25,7 +25,6 @@ import android.bluetooth.BluetoothStatusCodes;
import android.bluetooth.BluetoothUuid;
import android.bluetooth.BluetoothUuid;
import android.bluetooth.IBluetoothLeBroadcastAssistant;
import android.bluetooth.IBluetoothLeBroadcastAssistant;
import android.bluetooth.IBluetoothLeBroadcastAssistantCallback;
import android.bluetooth.IBluetoothLeBroadcastAssistantCallback;
import android.bluetooth.le.BluetoothLeScanner;
import android.bluetooth.le.ScanCallback;
import android.bluetooth.le.ScanCallback;
import android.bluetooth.le.ScanFilter;
import android.bluetooth.le.ScanFilter;
import android.bluetooth.le.ScanRecord;
import android.bluetooth.le.ScanRecord;
@@ -260,8 +259,7 @@ public class BassClientService extends ProfileService {
        }
        }
        synchronized (mStateMachines) {
        synchronized (mStateMachines) {
            for (BassClientStateMachine sm : mStateMachines.values()) {
            for (BassClientStateMachine sm : mStateMachines.values()) {
                sm.doQuit();
                BassObjectsFactory.getInstance().destroyStateMachine(sm);
                sm.cleanup();
            }
            }
            mStateMachines.clear();
            mStateMachines.clear();
        }
        }
@@ -369,8 +367,8 @@ public class BassClientService extends ProfileService {
                return null;
                return null;
            }
            }
            log("Creating a new state machine for " + device);
            log("Creating a new state machine for " + device);
            stateMachine = BassClientStateMachine.make(device,
            stateMachine = BassObjectsFactory.getInstance().makeStateMachine(
                    this, mStateMachinesThread.getLooper());
                    device, this, mStateMachinesThread.getLooper());
            mStateMachines.put(device, stateMachine);
            mStateMachines.put(device, stateMachine);
            return stateMachine;
            return stateMachine;
        }
        }
@@ -620,7 +618,8 @@ public class BassClientService extends ProfileService {
            Log.e(TAG, "startSearchingForSources: Adapter is NULL");
            Log.e(TAG, "startSearchingForSources: Adapter is NULL");
            return;
            return;
        }
        }
        BluetoothLeScanner scanner = mBluetoothAdapter.getBluetoothLeScanner();
        BluetoothLeScannerWrapper scanner = BassObjectsFactory.getInstance()
                .getBluetoothLeScannerWrapper(mBluetoothAdapter);
        if (scanner == null) {
        if (scanner == null) {
            Log.e(TAG, "startLeScan: cannot get BluetoothLeScanner");
            Log.e(TAG, "startLeScan: cannot get BluetoothLeScanner");
            return;
            return;
@@ -686,7 +685,12 @@ public class BassClientService extends ProfileService {
     */
     */
    public void stopSearchingForSources() {
    public void stopSearchingForSources() {
        log("stopSearchingForSources");
        log("stopSearchingForSources");
        BluetoothLeScanner scanner = mBluetoothAdapter.getBluetoothLeScanner();
        if (mBluetoothAdapter == null) {
            Log.e(TAG, "stopSearchingForSources: Adapter is NULL");
            return;
        }
        BluetoothLeScannerWrapper scanner = BassObjectsFactory.getInstance()
                .getBluetoothLeScannerWrapper(mBluetoothAdapter);
        if (scanner == null) {
        if (scanner == null) {
            Log.e(TAG, "startLeScan: cannot get BluetoothLeScanner");
            Log.e(TAG, "startLeScan: cannot get BluetoothLeScanner");
            return;
            return;
+12 −1
Original line number Original line Diff line number Diff line
@@ -100,7 +100,8 @@ import java.util.Map;
import java.util.Scanner;
import java.util.Scanner;
import java.util.stream.IntStream;
import java.util.stream.IntStream;


class BassClientStateMachine extends StateMachine {
@VisibleForTesting
public class BassClientStateMachine extends StateMachine {
    private static final String TAG = "BassClientStateMachine";
    private static final String TAG = "BassClientStateMachine";
    private static final byte[] REMOTE_SCAN_STOP = {00};
    private static final byte[] REMOTE_SCAN_STOP = {00};
    private static final byte[] REMOTE_SCAN_START = {01};
    private static final byte[] REMOTE_SCAN_START = {01};
@@ -201,6 +202,16 @@ class BassClientStateMachine extends StateMachine {
        return BassclientSm;
        return BassclientSm;
    }
    }


    static void destroy(BassClientStateMachine stateMachine) {
        Log.i(TAG, "destroy");
        if (stateMachine == null) {
            Log.w(TAG, "destroy(), stateMachine is null");
            return;
        }
        stateMachine.doQuit();
        stateMachine.cleanup();
    }

    public void doQuit() {
    public void doQuit() {
        log("doQuit for device " + mDevice);
        log("doQuit for device " + mDevice);
        quitNow();
        quitNow();
+96 −0
Original line number Original line Diff line number Diff line
/*
 * Copyright (C) 2022 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 com.android.bluetooth.bass_client;

import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.os.Looper;
import android.util.Log;

import com.android.bluetooth.Utils;
import com.android.internal.annotations.VisibleForTesting;

/**
 * Factory class for object initialization to help with unit testing
 */
public class BassObjectsFactory {
    private static final String TAG = BassObjectsFactory.class.getSimpleName();
    private static BassObjectsFactory sInstance;
    private static final Object INSTANCE_LOCK = new Object();

    private BassObjectsFactory() {}

    /**
     * Get the singleton instance of object factory
     *
     * @return the singleton instance, guaranteed not null
     */
    public static BassObjectsFactory getInstance() {
        synchronized (INSTANCE_LOCK) {
            if (sInstance == null) {
                sInstance = new BassObjectsFactory();
            }
        }
        return sInstance;
    }

    /**
     * Allow unit tests to substitute BassObjectsFactory with a test instance
     *
     * @param objectsFactory a test instance of the BassObjectsFactory
     */
    @VisibleForTesting
    public static void setInstanceForTesting(BassObjectsFactory objectsFactory) {
        Utils.enforceInstrumentationTestMode();
        synchronized (INSTANCE_LOCK) {
            Log.d(TAG, "setInstanceForTesting(), set to " + objectsFactory);
            sInstance = objectsFactory;
        }
    }

    /**
     * Make a {@link BassClientStateMachine}
     *
     * @param device the remote device associated with this state machine
     * @param svc the bass client service
     * @param looper the thread that the state machine is supposed to run on
     * @return a state machine that is initialized and started, ready to go
     */
    public BassClientStateMachine makeStateMachine(BluetoothDevice device,
            BassClientService svc, Looper looper) {
        return BassClientStateMachine.make(device, svc, looper);
    }

    /**
     * Destroy a state machine
     *
     * @param stateMachine to be destroyed. Cannot be used after this call.
     */
    public void destroyStateMachine(BassClientStateMachine stateMachine) {
        BassClientStateMachine.destroy(stateMachine);
    }

    /**
     * Get a {@link BluetoothLeScannerWrapper} object
     *
     * @param adapter bluetooth adapter
     * @return a bluetooth LE scanner
     */
    public BluetoothLeScannerWrapper getBluetoothLeScannerWrapper(BluetoothAdapter adapter) {
        return new BluetoothLeScannerWrapper(adapter.getBluetoothLeScanner());
    }
}
+51 −0
Original line number Original line Diff line number Diff line
/*
 * Copyright (C) 2022 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 com.android.bluetooth.bass_client;

import android.bluetooth.le.BluetoothLeScanner;
import android.bluetooth.le.ScanCallback;
import android.bluetooth.le.ScanFilter;
import android.bluetooth.le.ScanSettings;

import java.util.List;

/**
 * Helper class to mock {@link BluetoothLeScanner} which is final.
 */
public class BluetoothLeScannerWrapper {

    BluetoothLeScanner mBluetoothLeScanner;

    BluetoothLeScannerWrapper(BluetoothLeScanner scanner) {
        mBluetoothLeScanner = scanner;
    }

    /**
     * Starts Bluetooth LE scanning
     */
    public void startScan(List<ScanFilter> filters, ScanSettings settings,
            final ScanCallback callback) {
        mBluetoothLeScanner.startScan(filters, settings, callback);
    }

    /**
     * Stops Bluetooth LE scanning
     */
    public void stopScan(ScanCallback callback) {
        mBluetoothLeScanner.stopScan(callback);
    }
}
+222 −0
Original line number Original line Diff line number Diff line
/*
 * Copyright (C) 2022 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 com.android.bluetooth.bass_client;

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

import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.anyString;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.notNull;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothProfile;
import android.bluetooth.BluetoothUuid;
import android.bluetooth.le.ScanFilter;
import android.content.Context;
import android.os.ParcelUuid;

import androidx.test.InstrumentationRegistry;
import androidx.test.filters.MediumTest;
import androidx.test.rule.ServiceTestRule;
import androidx.test.runner.AndroidJUnit4;

import com.android.bluetooth.TestUtils;
import com.android.bluetooth.btservice.AdapterService;
import com.android.bluetooth.btservice.storage.DatabaseManager;

import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.mockito.Spy;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Set;

/**
 * Tests for {@link BassClientService}
 */
@MediumTest
@RunWith(AndroidJUnit4.class)
public class BassClientServiceTest {
    private static final int MAX_HEADSET_CONNECTIONS = 5;
    private static final ParcelUuid[] FAKE_SERVICE_UUIDS = {BluetoothUuid.BASS};
    private static final int ASYNC_CALL_TIMEOUT_MILLIS = 250;

    private final HashMap<BluetoothDevice, BassClientStateMachine> mStateMachines = new HashMap<>();

    private Context mTargetContext;
    private BassClientService mBassClientService;
    private BluetoothAdapter mBluetoothAdapter;
    private BluetoothDevice mCurrentDevice;

    @Rule public final ServiceTestRule mServiceRule = new ServiceTestRule();

    @Spy private BassObjectsFactory mObjectsFactory = BassObjectsFactory.getInstance();
    @Mock private AdapterService mAdapterService;
    @Mock private DatabaseManager mDatabaseManager;
    @Mock private BluetoothLeScannerWrapper mBluetoothLeScannerWrapper;

    @Before
    public void setUp() throws Exception {
        mTargetContext = InstrumentationRegistry.getTargetContext();
        MockitoAnnotations.initMocks(this);
        TestUtils.setAdapterService(mAdapterService);
        BassObjectsFactory.setInstanceForTesting(mObjectsFactory);

        doReturn(new ParcelUuid[]{BluetoothUuid.BASS}).when(mAdapterService)
                .getRemoteUuids(any(BluetoothDevice.class));
        // This line must be called to make sure relevant objects are initialized properly
        mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();

        // Mock methods in AdapterService
        doReturn(FAKE_SERVICE_UUIDS).when(mAdapterService)
                .getRemoteUuids(any(BluetoothDevice.class));
        doReturn(BluetoothDevice.BOND_BONDED).when(mAdapterService)
                .getBondState(any(BluetoothDevice.class));
        doReturn(mDatabaseManager).when(mAdapterService).getDatabase();
        doReturn(true, false).when(mAdapterService).isStartedProfile(anyString());
        doAnswer(invocation -> {
            Set<BluetoothDevice> keys = mStateMachines.keySet();
            return keys.toArray(new BluetoothDevice[keys.size()]);
        }).when(mAdapterService).getBondedDevices();

        // Mock methods in BassObjectsFactory
        doAnswer(invocation -> {
            assertThat(mCurrentDevice).isNotNull();
            final BassClientStateMachine stateMachine = mock(BassClientStateMachine.class);
            mStateMachines.put(mCurrentDevice, stateMachine);
            return stateMachine;
        }).when(mObjectsFactory).makeStateMachine(any(), any(), any());
        doReturn(mBluetoothLeScannerWrapper).when(mObjectsFactory)
                .getBluetoothLeScannerWrapper(any());

        TestUtils.startService(mServiceRule, BassClientService.class);
        mBassClientService = BassClientService.getBassClientService();
        assertThat(mBassClientService).isNotNull();
    }

    @After
    public void tearDown() throws Exception {
        TestUtils.stopService(mServiceRule, BassClientService.class);
        mBassClientService = BassClientService.getBassClientService();
        assertThat(mBassClientService).isNull();
        mStateMachines.clear();
        mCurrentDevice = null;
        BassObjectsFactory.setInstanceForTesting(null);
        TestUtils.clearAdapterService(mAdapterService);
    }

    /**
     * Test to verify that BassClientService can be successfully started
     */
    @Test
    public void testGetBassClientService() {
        assertThat(mBassClientService).isEqualTo(BassClientService.getBassClientService());
        // Verify default connection and audio states
        mCurrentDevice = TestUtils.getTestDevice(mBluetoothAdapter, 0);
        assertThat(mBassClientService.getConnectionState(mCurrentDevice))
                .isEqualTo(BluetoothProfile.STATE_DISCONNECTED);
    }

    /**
     * Test connecting to a test device.
     *  - service.connect() should return false
     *  - bassClientStateMachine.sendMessage(CONNECT) should be called.
     */
    @Test
    public void testConnect() {
        when(mDatabaseManager.getProfileConnectionPolicy(any(BluetoothDevice.class),
                eq(BluetoothProfile.LE_AUDIO_BROADCAST_ASSISTANT)))
                .thenReturn(BluetoothProfile.CONNECTION_POLICY_ALLOWED);
        mCurrentDevice = TestUtils.getTestDevice(mBluetoothAdapter, 0);

        assertThat(mBassClientService.connect(mCurrentDevice)).isTrue();
        verify(mObjectsFactory).makeStateMachine(
                eq(mCurrentDevice), eq(mBassClientService), any());
        BassClientStateMachine stateMachine = mStateMachines.get(mCurrentDevice);
        assertThat(stateMachine).isNotNull();
        verify(stateMachine).sendMessage(BassClientStateMachine.CONNECT);
    }

    /**
     * Test connecting to a null device.
     *  - service.connect() should return false.
     */
    @Test
    public void testConnect_nullDevice() {
        when(mDatabaseManager.getProfileConnectionPolicy(any(BluetoothDevice.class),
                eq(BluetoothProfile.LE_AUDIO_BROADCAST_ASSISTANT)))
                .thenReturn(BluetoothProfile.CONNECTION_POLICY_ALLOWED);
        BluetoothDevice nullDevice = null;

        assertThat(mBassClientService.connect(nullDevice)).isFalse();
    }

    /**
     * Test connecting to a device when the connection policy is unknown.
     *  - service.connect() should return false.
     */
    @Test
    public void testConnect_whenConnectionPolicyIsUnknown() {
        when(mDatabaseManager.getProfileConnectionPolicy(any(BluetoothDevice.class),
                eq(BluetoothProfile.LE_AUDIO_BROADCAST_ASSISTANT)))
                .thenReturn(BluetoothProfile.CONNECTION_POLICY_UNKNOWN);
        mCurrentDevice = TestUtils.getTestDevice(mBluetoothAdapter, 0);
        assertThat(mCurrentDevice).isNotNull();

        assertThat(mBassClientService.connect(mCurrentDevice)).isFalse();
    }

    /**
     * Test whether service.startSearchingForSources() calls BluetoothLeScannerWrapper.startScan().
     */
    @Test
    public void testStartSearchingForSources() {
        List<ScanFilter> scanFilters = new ArrayList<>();
        mBassClientService.startSearchingForSources(scanFilters);

        verify(mBluetoothLeScannerWrapper).startScan(notNull(), notNull(), notNull());
    }

    /**
     * Test whether service.startSearchingForSources() does not call
     * BluetoothLeScannerWrapper.startScan() when the scanner instance cannot be achieved.
     */
    @Test
    public void testStartSearchingForSources_whenScannerIsNull() {
        doReturn(null).when(mObjectsFactory).getBluetoothLeScannerWrapper(any());
        List<ScanFilter> scanFilters = new ArrayList<>();

        mBassClientService.startSearchingForSources(scanFilters);

        verify(mBluetoothLeScannerWrapper, never()).startScan(any(), any(), any());
    }
}