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.SysUISingleton;
|
||||||
import com.android.systemui.dagger.qualifiers.Main;
|
import com.android.systemui.dagger.qualifiers.Main;
|
||||||
import com.android.systemui.dump.DumpManager;
|
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.settings.UserTracker;
|
||||||
|
import com.android.systemui.statusbar.policy.bluetooth.BluetoothRepository;
|
||||||
|
import com.android.systemui.statusbar.policy.bluetooth.ConnectionStatusModel;
|
||||||
|
|
||||||
import java.io.PrintWriter;
|
import java.io.PrintWriter;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
@@ -50,14 +54,20 @@ import java.util.concurrent.Executor;
|
|||||||
import javax.inject.Inject;
|
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
|
@SysUISingleton
|
||||||
public class BluetoothControllerImpl implements BluetoothController, BluetoothCallback,
|
public class BluetoothControllerImpl implements BluetoothController, BluetoothCallback,
|
||||||
CachedBluetoothDevice.Callback, LocalBluetoothProfileManager.ServiceListener {
|
CachedBluetoothDevice.Callback, LocalBluetoothProfileManager.ServiceListener {
|
||||||
private static final String TAG = "BluetoothController";
|
private static final String TAG = "BluetoothController";
|
||||||
|
|
||||||
|
private final FeatureFlags mFeatureFlags;
|
||||||
private final DumpManager mDumpManager;
|
private final DumpManager mDumpManager;
|
||||||
private final BluetoothLogger mLogger;
|
private final BluetoothLogger mLogger;
|
||||||
|
private final BluetoothRepository mBluetoothRepository;
|
||||||
private final LocalBluetoothManager mLocalBluetoothManager;
|
private final LocalBluetoothManager mLocalBluetoothManager;
|
||||||
private final UserManager mUserManager;
|
private final UserManager mUserManager;
|
||||||
private final int mCurrentUser;
|
private final int mCurrentUser;
|
||||||
@@ -79,14 +89,18 @@ public class BluetoothControllerImpl implements BluetoothController, BluetoothCa
|
|||||||
@Inject
|
@Inject
|
||||||
public BluetoothControllerImpl(
|
public BluetoothControllerImpl(
|
||||||
Context context,
|
Context context,
|
||||||
|
FeatureFlags featureFlags,
|
||||||
UserTracker userTracker,
|
UserTracker userTracker,
|
||||||
DumpManager dumpManager,
|
DumpManager dumpManager,
|
||||||
BluetoothLogger logger,
|
BluetoothLogger logger,
|
||||||
|
BluetoothRepository bluetoothRepository,
|
||||||
@Main Looper mainLooper,
|
@Main Looper mainLooper,
|
||||||
@Nullable LocalBluetoothManager localBluetoothManager,
|
@Nullable LocalBluetoothManager localBluetoothManager,
|
||||||
@Nullable BluetoothAdapter bluetoothAdapter) {
|
@Nullable BluetoothAdapter bluetoothAdapter) {
|
||||||
|
mFeatureFlags = featureFlags;
|
||||||
mDumpManager = dumpManager;
|
mDumpManager = dumpManager;
|
||||||
mLogger = logger;
|
mLogger = logger;
|
||||||
|
mBluetoothRepository = bluetoothRepository;
|
||||||
mLocalBluetoothManager = localBluetoothManager;
|
mLocalBluetoothManager = localBluetoothManager;
|
||||||
mHandler = new H(mainLooper);
|
mHandler = new H(mainLooper);
|
||||||
if (mLocalBluetoothManager != null) {
|
if (mLocalBluetoothManager != null) {
|
||||||
@@ -229,6 +243,16 @@ public class BluetoothControllerImpl implements BluetoothController, BluetoothCa
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void updateConnected() {
|
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.
|
// Make sure our connection state is up to date.
|
||||||
int state = mLocalBluetoothManager.getBluetoothAdapter().getConnectionState();
|
int state = mLocalBluetoothManager.getBluetoothAdapter().getConnectionState();
|
||||||
List<CachedBluetoothDevice> newList = new ArrayList<>();
|
List<CachedBluetoothDevice> newList = new ArrayList<>();
|
||||||
@@ -249,6 +273,12 @@ public class BluetoothControllerImpl implements BluetoothController, BluetoothCa
|
|||||||
// connected.
|
// connected.
|
||||||
state = BluetoothAdapter.STATE_DISCONNECTED;
|
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) {
|
synchronized (mConnectedDevices) {
|
||||||
mConnectedDevices.clear();
|
mConnectedDevices.clear();
|
||||||
mConnectedDevices.addAll(newList);
|
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.WalletControllerImpl;
|
||||||
import com.android.systemui.statusbar.policy.ZenModeController;
|
import com.android.systemui.statusbar.policy.ZenModeController;
|
||||||
import com.android.systemui.statusbar.policy.ZenModeControllerImpl;
|
import com.android.systemui.statusbar.policy.ZenModeControllerImpl;
|
||||||
|
import com.android.systemui.statusbar.policy.bluetooth.BluetoothRepository;
|
||||||
import java.util.concurrent.Executor;
|
import com.android.systemui.statusbar.policy.bluetooth.BluetoothRepositoryImpl;
|
||||||
|
|
||||||
import javax.inject.Named;
|
|
||||||
|
|
||||||
import dagger.Binds;
|
import dagger.Binds;
|
||||||
import dagger.Module;
|
import dagger.Module;
|
||||||
import dagger.Provides;
|
import dagger.Provides;
|
||||||
|
|
||||||
|
import java.util.concurrent.Executor;
|
||||||
|
|
||||||
|
import javax.inject.Named;
|
||||||
|
|
||||||
/** Dagger Module for code in the statusbar.policy package. */
|
/** Dagger Module for code in the statusbar.policy package. */
|
||||||
@Module
|
@Module
|
||||||
@@ -82,6 +83,10 @@ public interface StatusBarPolicyModule {
|
|||||||
@Binds
|
@Binds
|
||||||
BluetoothController provideBluetoothController(BluetoothControllerImpl controllerImpl);
|
BluetoothController provideBluetoothController(BluetoothControllerImpl controllerImpl);
|
||||||
|
|
||||||
|
/** */
|
||||||
|
@Binds
|
||||||
|
BluetoothRepository provideBluetoothRepository(BluetoothRepositoryImpl impl);
|
||||||
|
|
||||||
/** */
|
/** */
|
||||||
@Binds
|
@Binds
|
||||||
CastController provideCastController(CastControllerImpl controllerImpl);
|
CastController provideCastController(CastControllerImpl controllerImpl);
|
||||||
|
|||||||
@@ -14,6 +14,8 @@
|
|||||||
|
|
||||||
package com.android.systemui.statusbar.policy;
|
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.assertEquals;
|
||||||
import static org.junit.Assert.assertFalse;
|
import static org.junit.Assert.assertFalse;
|
||||||
import static org.junit.Assert.assertTrue;
|
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.SysuiTestCase;
|
||||||
import com.android.systemui.bluetooth.BluetoothLogger;
|
import com.android.systemui.bluetooth.BluetoothLogger;
|
||||||
import com.android.systemui.dump.DumpManager;
|
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.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.concurrency.FakeExecutor;
|
||||||
import com.android.systemui.util.time.FakeSystemClock;
|
import com.android.systemui.util.time.FakeSystemClock;
|
||||||
|
|
||||||
@@ -69,6 +75,7 @@ public class BluetoothControllerImplTest extends SysuiTestCase {
|
|||||||
private DumpManager mMockDumpManager;
|
private DumpManager mMockDumpManager;
|
||||||
private BluetoothControllerImpl mBluetoothControllerImpl;
|
private BluetoothControllerImpl mBluetoothControllerImpl;
|
||||||
private BluetoothAdapter mMockAdapter;
|
private BluetoothAdapter mMockAdapter;
|
||||||
|
private final FakeFeatureFlags mFakeFeatureFlags = new FakeFeatureFlags();
|
||||||
|
|
||||||
private List<CachedBluetoothDevice> mDevices;
|
private List<CachedBluetoothDevice> mDevices;
|
||||||
|
|
||||||
@@ -89,17 +96,26 @@ public class BluetoothControllerImplTest extends SysuiTestCase {
|
|||||||
.thenReturn(mock(LocalBluetoothProfileManager.class));
|
.thenReturn(mock(LocalBluetoothProfileManager.class));
|
||||||
mMockDumpManager = mock(DumpManager.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,
|
mUserTracker,
|
||||||
mMockDumpManager,
|
mMockDumpManager,
|
||||||
mock(BluetoothLogger.class),
|
mock(BluetoothLogger.class),
|
||||||
|
bluetoothRepository,
|
||||||
mTestableLooper.getLooper(),
|
mTestableLooper.getLooper(),
|
||||||
mMockBluetoothManager,
|
mMockBluetoothManager,
|
||||||
mMockAdapter);
|
mMockAdapter);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testNoConnectionWithDevices() {
|
public void testNoConnectionWithDevices_repoFlagOff() {
|
||||||
|
mFakeFeatureFlags.set(Flags.NEW_BLUETOOTH_REPOSITORY, false);
|
||||||
|
|
||||||
CachedBluetoothDevice device = mock(CachedBluetoothDevice.class);
|
CachedBluetoothDevice device = mock(CachedBluetoothDevice.class);
|
||||||
when(device.isConnected()).thenReturn(true);
|
when(device.isConnected()).thenReturn(true);
|
||||||
when(device.getMaxConnectionState()).thenReturn(BluetoothProfile.STATE_CONNECTED);
|
when(device.getMaxConnectionState()).thenReturn(BluetoothProfile.STATE_CONNECTED);
|
||||||
@@ -113,7 +129,27 @@ public class BluetoothControllerImplTest extends SysuiTestCase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@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);
|
when(mMockLocalAdapter.getConnectionState()).thenReturn(BluetoothAdapter.STATE_CONNECTING);
|
||||||
|
|
||||||
mBluetoothControllerImpl.onServiceConnected();
|
mBluetoothControllerImpl.onServiceConnected();
|
||||||
@@ -122,6 +158,58 @@ public class BluetoothControllerImplTest extends SysuiTestCase {
|
|||||||
assertFalse(mBluetoothControllerImpl.isBluetoothConnected());
|
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
|
@Test
|
||||||
public void testOnBluetoothStateChange_updatesBluetoothState() {
|
public void testOnBluetoothStateChange_updatesBluetoothState() {
|
||||||
mBluetoothControllerImpl.onBluetoothStateChanged(BluetoothAdapter.STATE_OFF);
|
mBluetoothControllerImpl.onBluetoothStateChanged(BluetoothAdapter.STATE_OFF);
|
||||||
@@ -147,8 +235,9 @@ public class BluetoothControllerImplTest extends SysuiTestCase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testOnACLConnectionStateChange_updatesBluetoothStateOnConnection()
|
public void testOnACLConnectionStateChange_updatesBluetoothStateOnConnection_repoFlagOff() {
|
||||||
throws Exception {
|
mFakeFeatureFlags.set(Flags.NEW_BLUETOOTH_REPOSITORY, false);
|
||||||
|
|
||||||
BluetoothController.Callback callback = mock(BluetoothController.Callback.class);
|
BluetoothController.Callback callback = mock(BluetoothController.Callback.class);
|
||||||
mBluetoothControllerImpl.addCallback(callback);
|
mBluetoothControllerImpl.addCallback(callback);
|
||||||
|
|
||||||
@@ -167,6 +256,29 @@ public class BluetoothControllerImplTest extends SysuiTestCase {
|
|||||||
verify(callback, atLeastOnce()).onBluetoothStateChange(anyBoolean());
|
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
|
@Test
|
||||||
public void testOnActiveDeviceChanged_updatesAudioActive() {
|
public void testOnActiveDeviceChanged_updatesAudioActive() {
|
||||||
assertFalse(mBluetoothControllerImpl.isBluetoothAudioActive());
|
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