Merge "[Bluetooth] Define bluetooth repo to make fetches in the background." into udc-dev
This commit is contained in:
committed by
Android (Google) Code Review
commit
4c082a2d4c
@@ -38,7 +38,11 @@ import com.android.systemui.bluetooth.BluetoothLogger;
|
||||
import com.android.systemui.dagger.SysUISingleton;
|
||||
import com.android.systemui.dagger.qualifiers.Main;
|
||||
import com.android.systemui.dump.DumpManager;
|
||||
import com.android.systemui.flags.FeatureFlags;
|
||||
import com.android.systemui.flags.Flags;
|
||||
import com.android.systemui.settings.UserTracker;
|
||||
import com.android.systemui.statusbar.policy.bluetooth.BluetoothRepository;
|
||||
import com.android.systemui.statusbar.policy.bluetooth.ConnectionStatusModel;
|
||||
|
||||
import java.io.PrintWriter;
|
||||
import java.util.ArrayList;
|
||||
@@ -50,14 +54,20 @@ import java.util.concurrent.Executor;
|
||||
import javax.inject.Inject;
|
||||
|
||||
/**
|
||||
* Controller for information about bluetooth connections.
|
||||
*
|
||||
* Note: Right now, this class and {@link BluetoothRepository} co-exist. Any new code should go in
|
||||
* {@link BluetoothRepository}, but external clients should query this file for now.
|
||||
*/
|
||||
@SysUISingleton
|
||||
public class BluetoothControllerImpl implements BluetoothController, BluetoothCallback,
|
||||
CachedBluetoothDevice.Callback, LocalBluetoothProfileManager.ServiceListener {
|
||||
private static final String TAG = "BluetoothController";
|
||||
|
||||
private final FeatureFlags mFeatureFlags;
|
||||
private final DumpManager mDumpManager;
|
||||
private final BluetoothLogger mLogger;
|
||||
private final BluetoothRepository mBluetoothRepository;
|
||||
private final LocalBluetoothManager mLocalBluetoothManager;
|
||||
private final UserManager mUserManager;
|
||||
private final int mCurrentUser;
|
||||
@@ -79,14 +89,18 @@ public class BluetoothControllerImpl implements BluetoothController, BluetoothCa
|
||||
@Inject
|
||||
public BluetoothControllerImpl(
|
||||
Context context,
|
||||
FeatureFlags featureFlags,
|
||||
UserTracker userTracker,
|
||||
DumpManager dumpManager,
|
||||
BluetoothLogger logger,
|
||||
BluetoothRepository bluetoothRepository,
|
||||
@Main Looper mainLooper,
|
||||
@Nullable LocalBluetoothManager localBluetoothManager,
|
||||
@Nullable BluetoothAdapter bluetoothAdapter) {
|
||||
mFeatureFlags = featureFlags;
|
||||
mDumpManager = dumpManager;
|
||||
mLogger = logger;
|
||||
mBluetoothRepository = bluetoothRepository;
|
||||
mLocalBluetoothManager = localBluetoothManager;
|
||||
mHandler = new H(mainLooper);
|
||||
if (mLocalBluetoothManager != null) {
|
||||
@@ -229,6 +243,16 @@ public class BluetoothControllerImpl implements BluetoothController, BluetoothCa
|
||||
}
|
||||
|
||||
private void updateConnected() {
|
||||
if (mFeatureFlags.isEnabled(Flags.NEW_BLUETOOTH_REPOSITORY)) {
|
||||
mBluetoothRepository.fetchConnectionStatusInBackground(
|
||||
getDevices(), this::onConnectionStatusFetched);
|
||||
} else {
|
||||
updateConnectedOld();
|
||||
}
|
||||
}
|
||||
|
||||
/** Used only if {@link Flags.NEW_BLUETOOTH_REPOSITORY} is *not* enabled. */
|
||||
private void updateConnectedOld() {
|
||||
// Make sure our connection state is up to date.
|
||||
int state = mLocalBluetoothManager.getBluetoothAdapter().getConnectionState();
|
||||
List<CachedBluetoothDevice> newList = new ArrayList<>();
|
||||
@@ -249,6 +273,12 @@ public class BluetoothControllerImpl implements BluetoothController, BluetoothCa
|
||||
// connected.
|
||||
state = BluetoothAdapter.STATE_DISCONNECTED;
|
||||
}
|
||||
onConnectionStatusFetched(new ConnectionStatusModel(state, newList));
|
||||
}
|
||||
|
||||
private void onConnectionStatusFetched(ConnectionStatusModel status) {
|
||||
List<CachedBluetoothDevice> newList = status.getConnectedDevices();
|
||||
int state = status.getMaxConnectionState();
|
||||
synchronized (mConnectedDevices) {
|
||||
mConnectedDevices.clear();
|
||||
mConnectedDevices.addAll(newList);
|
||||
|
||||
@@ -0,0 +1,109 @@
|
||||
/*
|
||||
* 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 com.android.systemui.statusbar.policy.bluetooth
|
||||
|
||||
import android.bluetooth.BluetoothAdapter
|
||||
import android.bluetooth.BluetoothProfile
|
||||
import com.android.settingslib.bluetooth.CachedBluetoothDevice
|
||||
import com.android.settingslib.bluetooth.LocalBluetoothManager
|
||||
import com.android.systemui.dagger.SysUISingleton
|
||||
import com.android.systemui.dagger.qualifiers.Application
|
||||
import com.android.systemui.dagger.qualifiers.Background
|
||||
import javax.inject.Inject
|
||||
import kotlinx.coroutines.CoroutineDispatcher
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
/**
|
||||
* Repository for information about bluetooth connections.
|
||||
*
|
||||
* Note: Right now, this class and [BluetoothController] co-exist. Any new code should go in this
|
||||
* implementation, but external clients should query [BluetoothController] instead of this class for
|
||||
* now.
|
||||
*/
|
||||
interface BluetoothRepository {
|
||||
/**
|
||||
* Fetches the connection statuses for the given [currentDevices] and invokes [callback] once
|
||||
* those statuses have been fetched. The fetching occurs on a background thread because IPCs may
|
||||
* be required to fetch the statuses (see b/271058380).
|
||||
*/
|
||||
fun fetchConnectionStatusInBackground(
|
||||
currentDevices: Collection<CachedBluetoothDevice>,
|
||||
callback: ConnectionStatusFetchedCallback,
|
||||
)
|
||||
}
|
||||
|
||||
/** Implementation of [BluetoothRepository]. */
|
||||
@SysUISingleton
|
||||
class BluetoothRepositoryImpl
|
||||
@Inject
|
||||
constructor(
|
||||
@Application private val scope: CoroutineScope,
|
||||
@Background private val bgDispatcher: CoroutineDispatcher,
|
||||
private val localBluetoothManager: LocalBluetoothManager?,
|
||||
) : BluetoothRepository {
|
||||
override fun fetchConnectionStatusInBackground(
|
||||
currentDevices: Collection<CachedBluetoothDevice>,
|
||||
callback: ConnectionStatusFetchedCallback,
|
||||
) {
|
||||
scope.launch {
|
||||
val result = fetchConnectionStatus(currentDevices)
|
||||
callback.onConnectionStatusFetched(result)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun fetchConnectionStatus(
|
||||
currentDevices: Collection<CachedBluetoothDevice>,
|
||||
): ConnectionStatusModel {
|
||||
return withContext(bgDispatcher) {
|
||||
val minimumMaxConnectionState =
|
||||
localBluetoothManager?.bluetoothAdapter?.connectionState
|
||||
?: BluetoothProfile.STATE_DISCONNECTED
|
||||
var maxConnectionState =
|
||||
if (currentDevices.isEmpty()) {
|
||||
minimumMaxConnectionState
|
||||
} else {
|
||||
currentDevices
|
||||
.maxOf { it.maxConnectionState }
|
||||
.coerceAtLeast(minimumMaxConnectionState)
|
||||
}
|
||||
|
||||
val connectedDevices = currentDevices.filter { it.isConnected }
|
||||
|
||||
if (
|
||||
connectedDevices.isEmpty() && maxConnectionState == BluetoothAdapter.STATE_CONNECTED
|
||||
) {
|
||||
// If somehow we think we are connected, but have no connected devices, we aren't
|
||||
// connected.
|
||||
maxConnectionState = BluetoothAdapter.STATE_DISCONNECTED
|
||||
}
|
||||
|
||||
ConnectionStatusModel(maxConnectionState, connectedDevices)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
data class ConnectionStatusModel(
|
||||
/** The maximum connection state out of all current devices. */
|
||||
val maxConnectionState: Int,
|
||||
/** A list of devices that are currently connected. */
|
||||
val connectedDevices: List<CachedBluetoothDevice>,
|
||||
)
|
||||
|
||||
/** Callback notified when the new status has been fetched. */
|
||||
fun interface ConnectionStatusFetchedCallback {
|
||||
fun onConnectionStatusFetched(status: ConnectionStatusModel)
|
||||
}
|
||||
@@ -62,15 +62,16 @@ import com.android.systemui.statusbar.policy.WalletController;
|
||||
import com.android.systemui.statusbar.policy.WalletControllerImpl;
|
||||
import com.android.systemui.statusbar.policy.ZenModeController;
|
||||
import com.android.systemui.statusbar.policy.ZenModeControllerImpl;
|
||||
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
import javax.inject.Named;
|
||||
import com.android.systemui.statusbar.policy.bluetooth.BluetoothRepository;
|
||||
import com.android.systemui.statusbar.policy.bluetooth.BluetoothRepositoryImpl;
|
||||
|
||||
import dagger.Binds;
|
||||
import dagger.Module;
|
||||
import dagger.Provides;
|
||||
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
import javax.inject.Named;
|
||||
|
||||
/** Dagger Module for code in the statusbar.policy package. */
|
||||
@Module
|
||||
@@ -82,6 +83,10 @@ public interface StatusBarPolicyModule {
|
||||
@Binds
|
||||
BluetoothController provideBluetoothController(BluetoothControllerImpl controllerImpl);
|
||||
|
||||
/** */
|
||||
@Binds
|
||||
BluetoothRepository provideBluetoothRepository(BluetoothRepositoryImpl impl);
|
||||
|
||||
/** */
|
||||
@Binds
|
||||
CastController provideCastController(CastControllerImpl controllerImpl);
|
||||
|
||||
@@ -14,6 +14,8 @@
|
||||
|
||||
package com.android.systemui.statusbar.policy;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
@@ -44,7 +46,11 @@ import com.android.settingslib.bluetooth.LocalBluetoothProfileManager;
|
||||
import com.android.systemui.SysuiTestCase;
|
||||
import com.android.systemui.bluetooth.BluetoothLogger;
|
||||
import com.android.systemui.dump.DumpManager;
|
||||
import com.android.systemui.flags.FakeFeatureFlags;
|
||||
import com.android.systemui.flags.Flags;
|
||||
import com.android.systemui.settings.UserTracker;
|
||||
import com.android.systemui.statusbar.policy.bluetooth.BluetoothRepository;
|
||||
import com.android.systemui.statusbar.policy.bluetooth.FakeBluetoothRepository;
|
||||
import com.android.systemui.util.concurrency.FakeExecutor;
|
||||
import com.android.systemui.util.time.FakeSystemClock;
|
||||
|
||||
@@ -69,6 +75,7 @@ public class BluetoothControllerImplTest extends SysuiTestCase {
|
||||
private DumpManager mMockDumpManager;
|
||||
private BluetoothControllerImpl mBluetoothControllerImpl;
|
||||
private BluetoothAdapter mMockAdapter;
|
||||
private final FakeFeatureFlags mFakeFeatureFlags = new FakeFeatureFlags();
|
||||
|
||||
private List<CachedBluetoothDevice> mDevices;
|
||||
|
||||
@@ -89,17 +96,26 @@ public class BluetoothControllerImplTest extends SysuiTestCase {
|
||||
.thenReturn(mock(LocalBluetoothProfileManager.class));
|
||||
mMockDumpManager = mock(DumpManager.class);
|
||||
|
||||
mBluetoothControllerImpl = new BluetoothControllerImpl(mContext,
|
||||
BluetoothRepository bluetoothRepository =
|
||||
new FakeBluetoothRepository(mMockBluetoothManager);
|
||||
mFakeFeatureFlags.set(Flags.NEW_BLUETOOTH_REPOSITORY, true);
|
||||
|
||||
mBluetoothControllerImpl = new BluetoothControllerImpl(
|
||||
mContext,
|
||||
mFakeFeatureFlags,
|
||||
mUserTracker,
|
||||
mMockDumpManager,
|
||||
mock(BluetoothLogger.class),
|
||||
bluetoothRepository,
|
||||
mTestableLooper.getLooper(),
|
||||
mMockBluetoothManager,
|
||||
mMockAdapter);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNoConnectionWithDevices() {
|
||||
public void testNoConnectionWithDevices_repoFlagOff() {
|
||||
mFakeFeatureFlags.set(Flags.NEW_BLUETOOTH_REPOSITORY, false);
|
||||
|
||||
CachedBluetoothDevice device = mock(CachedBluetoothDevice.class);
|
||||
when(device.isConnected()).thenReturn(true);
|
||||
when(device.getMaxConnectionState()).thenReturn(BluetoothProfile.STATE_CONNECTED);
|
||||
@@ -113,7 +129,27 @@ public class BluetoothControllerImplTest extends SysuiTestCase {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOnServiceConnected_updatesConnectionState() {
|
||||
public void testNoConnectionWithDevices_repoFlagOn() {
|
||||
mFakeFeatureFlags.set(Flags.NEW_BLUETOOTH_REPOSITORY, true);
|
||||
|
||||
CachedBluetoothDevice device = mock(CachedBluetoothDevice.class);
|
||||
when(device.isConnected()).thenReturn(true);
|
||||
when(device.getMaxConnectionState()).thenReturn(BluetoothProfile.STATE_CONNECTED);
|
||||
|
||||
mDevices.add(device);
|
||||
when(mMockLocalAdapter.getConnectionState())
|
||||
.thenReturn(BluetoothAdapter.STATE_DISCONNECTED);
|
||||
|
||||
mBluetoothControllerImpl.onConnectionStateChanged(null,
|
||||
BluetoothAdapter.STATE_DISCONNECTED);
|
||||
|
||||
assertTrue(mBluetoothControllerImpl.isBluetoothConnected());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOnServiceConnected_updatesConnectionState_repoFlagOff() {
|
||||
mFakeFeatureFlags.set(Flags.NEW_BLUETOOTH_REPOSITORY, false);
|
||||
|
||||
when(mMockLocalAdapter.getConnectionState()).thenReturn(BluetoothAdapter.STATE_CONNECTING);
|
||||
|
||||
mBluetoothControllerImpl.onServiceConnected();
|
||||
@@ -122,6 +158,58 @@ public class BluetoothControllerImplTest extends SysuiTestCase {
|
||||
assertFalse(mBluetoothControllerImpl.isBluetoothConnected());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOnServiceConnected_updatesConnectionState_repoFlagOn() {
|
||||
mFakeFeatureFlags.set(Flags.NEW_BLUETOOTH_REPOSITORY, true);
|
||||
|
||||
when(mMockLocalAdapter.getConnectionState()).thenReturn(BluetoothAdapter.STATE_CONNECTING);
|
||||
|
||||
mBluetoothControllerImpl.onServiceConnected();
|
||||
|
||||
assertTrue(mBluetoothControllerImpl.isBluetoothConnecting());
|
||||
assertFalse(mBluetoothControllerImpl.isBluetoothConnected());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getConnectedDevices_onlyReturnsConnected_repoFlagOff() {
|
||||
mFakeFeatureFlags.set(Flags.NEW_BLUETOOTH_REPOSITORY, false);
|
||||
|
||||
CachedBluetoothDevice device1Disconnected = mock(CachedBluetoothDevice.class);
|
||||
when(device1Disconnected.isConnected()).thenReturn(false);
|
||||
mDevices.add(device1Disconnected);
|
||||
|
||||
CachedBluetoothDevice device2Connected = mock(CachedBluetoothDevice.class);
|
||||
when(device2Connected.isConnected()).thenReturn(true);
|
||||
mDevices.add(device2Connected);
|
||||
|
||||
mBluetoothControllerImpl.onDeviceAdded(device1Disconnected);
|
||||
mBluetoothControllerImpl.onDeviceAdded(device2Connected);
|
||||
|
||||
assertThat(mBluetoothControllerImpl.getConnectedDevices()).hasSize(1);
|
||||
assertThat(mBluetoothControllerImpl.getConnectedDevices().get(0))
|
||||
.isEqualTo(device2Connected);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getConnectedDevices_onlyReturnsConnected_repoFlagOn() {
|
||||
mFakeFeatureFlags.set(Flags.NEW_BLUETOOTH_REPOSITORY, true);
|
||||
|
||||
CachedBluetoothDevice device1Disconnected = mock(CachedBluetoothDevice.class);
|
||||
when(device1Disconnected.isConnected()).thenReturn(false);
|
||||
mDevices.add(device1Disconnected);
|
||||
|
||||
CachedBluetoothDevice device2Connected = mock(CachedBluetoothDevice.class);
|
||||
when(device2Connected.isConnected()).thenReturn(true);
|
||||
mDevices.add(device2Connected);
|
||||
|
||||
mBluetoothControllerImpl.onDeviceAdded(device1Disconnected);
|
||||
mBluetoothControllerImpl.onDeviceAdded(device2Connected);
|
||||
|
||||
assertThat(mBluetoothControllerImpl.getConnectedDevices()).hasSize(1);
|
||||
assertThat(mBluetoothControllerImpl.getConnectedDevices().get(0))
|
||||
.isEqualTo(device2Connected);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOnBluetoothStateChange_updatesBluetoothState() {
|
||||
mBluetoothControllerImpl.onBluetoothStateChanged(BluetoothAdapter.STATE_OFF);
|
||||
@@ -147,8 +235,9 @@ public class BluetoothControllerImplTest extends SysuiTestCase {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOnACLConnectionStateChange_updatesBluetoothStateOnConnection()
|
||||
throws Exception {
|
||||
public void testOnACLConnectionStateChange_updatesBluetoothStateOnConnection_repoFlagOff() {
|
||||
mFakeFeatureFlags.set(Flags.NEW_BLUETOOTH_REPOSITORY, false);
|
||||
|
||||
BluetoothController.Callback callback = mock(BluetoothController.Callback.class);
|
||||
mBluetoothControllerImpl.addCallback(callback);
|
||||
|
||||
@@ -167,6 +256,29 @@ public class BluetoothControllerImplTest extends SysuiTestCase {
|
||||
verify(callback, atLeastOnce()).onBluetoothStateChange(anyBoolean());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOnACLConnectionStateChange_updatesBluetoothStateOnConnection_repoFlagOn() {
|
||||
mFakeFeatureFlags.set(Flags.NEW_BLUETOOTH_REPOSITORY, true);
|
||||
|
||||
BluetoothController.Callback callback = mock(BluetoothController.Callback.class);
|
||||
mBluetoothControllerImpl.addCallback(callback);
|
||||
|
||||
assertFalse(mBluetoothControllerImpl.isBluetoothConnected());
|
||||
CachedBluetoothDevice device = mock(CachedBluetoothDevice.class);
|
||||
mDevices.add(device);
|
||||
when(device.isConnected()).thenReturn(true);
|
||||
when(device.getMaxConnectionState()).thenReturn(BluetoothProfile.STATE_CONNECTED);
|
||||
reset(callback);
|
||||
mBluetoothControllerImpl.onAclConnectionStateChanged(device,
|
||||
BluetoothProfile.STATE_CONNECTED);
|
||||
|
||||
mTestableLooper.processAllMessages();
|
||||
|
||||
assertTrue(mBluetoothControllerImpl.isBluetoothConnected());
|
||||
verify(callback, atLeastOnce()).onBluetoothStateChange(anyBoolean());
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testOnActiveDeviceChanged_updatesAudioActive() {
|
||||
assertFalse(mBluetoothControllerImpl.isBluetoothAudioActive());
|
||||
|
||||
@@ -0,0 +1,213 @@
|
||||
/*
|
||||
* 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 com.android.systemui.statusbar.policy.bluetooth
|
||||
|
||||
import android.bluetooth.BluetoothProfile
|
||||
import androidx.test.filters.SmallTest
|
||||
import com.android.settingslib.bluetooth.CachedBluetoothDevice
|
||||
import com.android.settingslib.bluetooth.LocalBluetoothAdapter
|
||||
import com.android.settingslib.bluetooth.LocalBluetoothManager
|
||||
import com.android.systemui.SysuiTestCase
|
||||
import com.android.systemui.util.mockito.mock
|
||||
import com.android.systemui.util.mockito.whenever
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.test.StandardTestDispatcher
|
||||
import kotlinx.coroutines.test.TestCoroutineScheduler
|
||||
import kotlinx.coroutines.test.TestDispatcher
|
||||
import kotlinx.coroutines.test.TestScope
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.mockito.Mock
|
||||
import org.mockito.MockitoAnnotations
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
@SmallTest
|
||||
class BluetoothRepositoryImplTest : SysuiTestCase() {
|
||||
|
||||
private lateinit var underTest: BluetoothRepositoryImpl
|
||||
|
||||
private lateinit var scheduler: TestCoroutineScheduler
|
||||
private lateinit var dispatcher: TestDispatcher
|
||||
private lateinit var testScope: TestScope
|
||||
|
||||
@Mock private lateinit var localBluetoothManager: LocalBluetoothManager
|
||||
@Mock private lateinit var bluetoothAdapter: LocalBluetoothAdapter
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
MockitoAnnotations.initMocks(this)
|
||||
whenever(localBluetoothManager.bluetoothAdapter).thenReturn(bluetoothAdapter)
|
||||
|
||||
scheduler = TestCoroutineScheduler()
|
||||
dispatcher = StandardTestDispatcher(scheduler)
|
||||
testScope = TestScope(dispatcher)
|
||||
|
||||
underTest =
|
||||
BluetoothRepositoryImpl(testScope.backgroundScope, dispatcher, localBluetoothManager)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun fetchConnectionStatusInBackground_currentDevicesEmpty_maxStateIsManagerState() {
|
||||
whenever(bluetoothAdapter.connectionState).thenReturn(BluetoothProfile.STATE_CONNECTING)
|
||||
|
||||
val status = fetchConnectionStatus(currentDevices = emptyList())
|
||||
|
||||
assertThat(status.maxConnectionState).isEqualTo(BluetoothProfile.STATE_CONNECTING)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun fetchConnectionStatusInBackground_currentDevicesEmpty_nullManager_maxStateIsDisconnected() {
|
||||
// This CONNECTING state should be unused because localBluetoothManager is null
|
||||
whenever(bluetoothAdapter.connectionState).thenReturn(BluetoothProfile.STATE_CONNECTING)
|
||||
underTest =
|
||||
BluetoothRepositoryImpl(
|
||||
testScope.backgroundScope,
|
||||
dispatcher,
|
||||
localBluetoothManager = null,
|
||||
)
|
||||
|
||||
val status = fetchConnectionStatus(currentDevices = emptyList())
|
||||
|
||||
assertThat(status.maxConnectionState).isEqualTo(BluetoothProfile.STATE_DISCONNECTED)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun fetchConnectionStatusInBackground_managerStateLargerThanDeviceStates_maxStateIsManager() {
|
||||
whenever(bluetoothAdapter.connectionState).thenReturn(BluetoothProfile.STATE_CONNECTING)
|
||||
val device1 =
|
||||
mock<CachedBluetoothDevice>().also {
|
||||
whenever(it.maxConnectionState).thenReturn(BluetoothProfile.STATE_DISCONNECTED)
|
||||
}
|
||||
val device2 =
|
||||
mock<CachedBluetoothDevice>().also {
|
||||
whenever(it.maxConnectionState).thenReturn(BluetoothProfile.STATE_DISCONNECTED)
|
||||
}
|
||||
|
||||
val status = fetchConnectionStatus(currentDevices = listOf(device1, device2))
|
||||
|
||||
assertThat(status.maxConnectionState).isEqualTo(BluetoothProfile.STATE_CONNECTING)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun fetchConnectionStatusInBackground_oneCurrentDevice_maxStateIsDeviceState() {
|
||||
whenever(bluetoothAdapter.connectionState).thenReturn(BluetoothProfile.STATE_DISCONNECTED)
|
||||
val device =
|
||||
mock<CachedBluetoothDevice>().also {
|
||||
whenever(it.maxConnectionState).thenReturn(BluetoothProfile.STATE_CONNECTING)
|
||||
}
|
||||
|
||||
val status = fetchConnectionStatus(currentDevices = listOf(device))
|
||||
|
||||
assertThat(status.maxConnectionState).isEqualTo(BluetoothProfile.STATE_CONNECTING)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun fetchConnectionStatusInBackground_multipleDevices_maxStateIsHighestState() {
|
||||
whenever(bluetoothAdapter.connectionState).thenReturn(BluetoothProfile.STATE_DISCONNECTED)
|
||||
|
||||
val device1 =
|
||||
mock<CachedBluetoothDevice>().also {
|
||||
whenever(it.maxConnectionState).thenReturn(BluetoothProfile.STATE_CONNECTING)
|
||||
whenever(it.isConnected).thenReturn(false)
|
||||
}
|
||||
val device2 =
|
||||
mock<CachedBluetoothDevice>().also {
|
||||
whenever(it.maxConnectionState).thenReturn(BluetoothProfile.STATE_CONNECTED)
|
||||
whenever(it.isConnected).thenReturn(true)
|
||||
}
|
||||
|
||||
val status = fetchConnectionStatus(currentDevices = listOf(device1, device2))
|
||||
|
||||
assertThat(status.maxConnectionState).isEqualTo(BluetoothProfile.STATE_CONNECTED)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun fetchConnectionStatusInBackground_devicesNotConnected_maxStateIsDisconnected() {
|
||||
whenever(bluetoothAdapter.connectionState).thenReturn(BluetoothProfile.STATE_CONNECTING)
|
||||
|
||||
// WHEN the devices say their state is CONNECTED but [isConnected] is false
|
||||
val device1 =
|
||||
mock<CachedBluetoothDevice>().also {
|
||||
whenever(it.maxConnectionState).thenReturn(BluetoothProfile.STATE_CONNECTED)
|
||||
whenever(it.isConnected).thenReturn(false)
|
||||
}
|
||||
val device2 =
|
||||
mock<CachedBluetoothDevice>().also {
|
||||
whenever(it.maxConnectionState).thenReturn(BluetoothProfile.STATE_CONNECTED)
|
||||
whenever(it.isConnected).thenReturn(false)
|
||||
}
|
||||
|
||||
val status = fetchConnectionStatus(currentDevices = listOf(device1, device2))
|
||||
|
||||
// THEN the max state is DISCONNECTED
|
||||
assertThat(status.maxConnectionState).isEqualTo(BluetoothProfile.STATE_DISCONNECTED)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun fetchConnectionStatusInBackground_currentDevicesEmpty_connectedDevicesEmpty() {
|
||||
val status = fetchConnectionStatus(currentDevices = emptyList())
|
||||
|
||||
assertThat(status.connectedDevices).isEmpty()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun fetchConnectionStatusInBackground_oneCurrentDeviceDisconnected_connectedDevicesEmpty() {
|
||||
val device =
|
||||
mock<CachedBluetoothDevice>().also { whenever(it.isConnected).thenReturn(false) }
|
||||
|
||||
val status = fetchConnectionStatus(currentDevices = listOf(device))
|
||||
|
||||
assertThat(status.connectedDevices).isEmpty()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun fetchConnectionStatusInBackground_oneCurrentDeviceConnected_connectedDevicesHasDevice() {
|
||||
val device =
|
||||
mock<CachedBluetoothDevice>().also { whenever(it.isConnected).thenReturn(true) }
|
||||
|
||||
val status = fetchConnectionStatus(currentDevices = listOf(device))
|
||||
|
||||
assertThat(status.connectedDevices).isEqualTo(listOf(device))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun fetchConnectionStatusInBackground_multipleDevices_connectedDevicesHasOnlyConnected() {
|
||||
val device1Connected =
|
||||
mock<CachedBluetoothDevice>().also { whenever(it.isConnected).thenReturn(true) }
|
||||
val device2Disconnected =
|
||||
mock<CachedBluetoothDevice>().also { whenever(it.isConnected).thenReturn(false) }
|
||||
val device3Connected =
|
||||
mock<CachedBluetoothDevice>().also { whenever(it.isConnected).thenReturn(true) }
|
||||
|
||||
val status =
|
||||
fetchConnectionStatus(
|
||||
currentDevices = listOf(device1Connected, device2Disconnected, device3Connected)
|
||||
)
|
||||
|
||||
assertThat(status.connectedDevices).isEqualTo(listOf(device1Connected, device3Connected))
|
||||
}
|
||||
|
||||
private fun fetchConnectionStatus(
|
||||
currentDevices: Collection<CachedBluetoothDevice>
|
||||
): ConnectionStatusModel {
|
||||
var receivedStatus: ConnectionStatusModel? = null
|
||||
underTest.fetchConnectionStatusInBackground(currentDevices) { status ->
|
||||
receivedStatus = status
|
||||
}
|
||||
scheduler.runCurrent()
|
||||
return receivedStatus!!
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* 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 com.android.systemui.statusbar.policy.bluetooth
|
||||
|
||||
import com.android.settingslib.bluetooth.CachedBluetoothDevice
|
||||
import com.android.settingslib.bluetooth.LocalBluetoothManager
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.test.StandardTestDispatcher
|
||||
import kotlinx.coroutines.test.TestCoroutineScheduler
|
||||
import kotlinx.coroutines.test.TestScope
|
||||
|
||||
/**
|
||||
* Fake [BluetoothRepository] that delegates to the real [BluetoothRepositoryImpl].
|
||||
*
|
||||
* We only need this because [BluetoothRepository] is called from Java, which can't use [TestScope],
|
||||
* [StandardTestDispatcher], etc. to create a test version of the repo. This class uses those test
|
||||
* items under-the-hood so Java classes can indirectly access them.
|
||||
*/
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
class FakeBluetoothRepository(localBluetoothManager: LocalBluetoothManager) : BluetoothRepository {
|
||||
|
||||
private val scheduler = TestCoroutineScheduler()
|
||||
private val dispatcher = StandardTestDispatcher(scheduler)
|
||||
private val testScope = TestScope(dispatcher)
|
||||
|
||||
private val impl =
|
||||
BluetoothRepositoryImpl(testScope.backgroundScope, dispatcher, localBluetoothManager)
|
||||
|
||||
override fun fetchConnectionStatusInBackground(
|
||||
currentDevices: Collection<CachedBluetoothDevice>,
|
||||
callback: ConnectionStatusFetchedCallback
|
||||
) {
|
||||
impl.fetchConnectionStatusInBackground(currentDevices, callback)
|
||||
scheduler.runCurrent()
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user