diff options
| author | 2023-03-22 15:37:04 +0000 | |
|---|---|---|
| committer | 2023-03-22 15:37:04 +0000 | |
| commit | b9a7b803ef1c2034cd9e8a8bf9384f8188f36b25 (patch) | |
| tree | 84c7070a9ea33b2a90b1c0b94c2484a16faebd03 | |
| parent | 300f56c8400bd1f2d539d3a9cf6e4982204b8d78 (diff) | |
| parent | 50bbe7cd1e86e745b3c97aafb543be435f464160 (diff) | |
Merge "Use FastPair battery level in blueooth QS tile." into udc-dev
6 files changed, 308 insertions, 69 deletions
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java index df1c8dfdde96..08fe2709b810 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java @@ -18,24 +18,26 @@ package com.android.systemui.qs.tiles; import static com.android.systemui.util.PluralMessageFormaterKt.icuMessageFormat; +import android.annotation.Nullable; import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothClass; import android.bluetooth.BluetoothDevice; import android.content.Intent; import android.os.Handler; +import android.os.HandlerExecutor; import android.os.Looper; import android.os.UserManager; import android.provider.Settings; import android.service.quicksettings.Tile; import android.text.TextUtils; +import android.util.Log; import android.view.View; import android.widget.Switch; -import androidx.annotation.Nullable; - import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.settingslib.Utils; +import com.android.settingslib.bluetooth.BluetoothUtils; import com.android.settingslib.bluetooth.CachedBluetoothDevice; import com.android.systemui.R; import com.android.systemui.dagger.qualifiers.Background; @@ -50,6 +52,7 @@ import com.android.systemui.qs.tileimpl.QSTileImpl; import com.android.systemui.statusbar.policy.BluetoothController; import java.util.List; +import java.util.concurrent.Executor; import javax.inject.Inject; @@ -60,8 +63,14 @@ public class BluetoothTile extends QSTileImpl<BooleanState> { private static final Intent BLUETOOTH_SETTINGS = new Intent(Settings.ACTION_BLUETOOTH_SETTINGS); + private static final String TAG = BluetoothTile.class.getSimpleName(); + private final BluetoothController mController; + private CachedBluetoothDevice mMetadataRegisteredDevice = null; + + private final Executor mExecutor; + @Inject public BluetoothTile( QSHost host, @@ -78,6 +87,7 @@ public class BluetoothTile extends QSTileImpl<BooleanState> { statusBarStateController, activityStarter, qsLogger); mController = bluetoothController; mController.observe(getLifecycle(), mCallback); + mExecutor = new HandlerExecutor(mainHandler); } @Override @@ -117,6 +127,15 @@ public class BluetoothTile extends QSTileImpl<BooleanState> { } @Override + protected void handleSetListening(boolean listening) { + super.handleSetListening(listening); + + if (!listening) { + stopListeningToStaleDeviceMetadata(); + } + } + + @Override protected void handleUpdateState(BooleanState state, Object arg) { checkIfRestrictionEnforcedByAdminOnly(state, UserManager.DISALLOW_BLUETOOTH); final boolean transientEnabling = arg == ARG_SHOW_TRANSIENT_ENABLING; @@ -125,6 +144,9 @@ public class BluetoothTile extends QSTileImpl<BooleanState> { final boolean connecting = mController.isBluetoothConnecting(); state.isTransient = transientEnabling || connecting || mController.getBluetoothState() == BluetoothAdapter.STATE_TURNING_ON; + if (!enabled || !connected || state.isTransient) { + stopListeningToStaleDeviceMetadata(); + } state.dualTarget = true; state.value = enabled; if (state.slash == null) { @@ -187,23 +209,32 @@ public class BluetoothTile extends QSTileImpl<BooleanState> { List<CachedBluetoothDevice> connectedDevices = mController.getConnectedDevices(); if (enabled && connected && !connectedDevices.isEmpty()) { if (connectedDevices.size() > 1) { + stopListeningToStaleDeviceMetadata(); return icuMessageFormat(mContext.getResources(), R.string.quick_settings_hotspot_secondary_label_num_devices, connectedDevices.size()); } - CachedBluetoothDevice lastDevice = connectedDevices.get(0); - final int batteryLevel = lastDevice.getBatteryLevel(); + CachedBluetoothDevice device = connectedDevices.get(0); + + // Use battery level provided by FastPair metadata if available. + // If not, fallback to the default battery level from bluetooth. + int batteryLevel = getMetadataBatteryLevel(device); + if (batteryLevel > BluetoothUtils.META_INT_ERROR) { + listenToMetadata(device); + } else { + stopListeningToStaleDeviceMetadata(); + batteryLevel = device.getBatteryLevel(); + } if (batteryLevel > BluetoothDevice.BATTERY_LEVEL_UNKNOWN) { return mContext.getString( R.string.quick_settings_bluetooth_secondary_label_battery_level, Utils.formatPercentage(batteryLevel)); - } else { - final BluetoothClass bluetoothClass = lastDevice.getBtClass(); + final BluetoothClass bluetoothClass = device.getBtClass(); if (bluetoothClass != null) { - if (lastDevice.isHearingAidDevice()) { + if (device.isHearingAidDevice()) { return mContext.getString( R.string.quick_settings_bluetooth_secondary_label_hearing_aids); } else if (bluetoothClass.doesClassMatch(BluetoothClass.PROFILE_A2DP)) { @@ -233,6 +264,36 @@ public class BluetoothTile extends QSTileImpl<BooleanState> { return mController.isBluetoothSupported(); } + private int getMetadataBatteryLevel(CachedBluetoothDevice device) { + return BluetoothUtils.getIntMetaData(device.getDevice(), + BluetoothDevice.METADATA_MAIN_BATTERY); + } + + private void listenToMetadata(CachedBluetoothDevice cachedDevice) { + if (cachedDevice == mMetadataRegisteredDevice) return; + stopListeningToStaleDeviceMetadata(); + try { + mController.addOnMetadataChangedListener(cachedDevice, + mExecutor, + mMetadataChangedListener); + mMetadataRegisteredDevice = cachedDevice; + } catch (IllegalArgumentException e) { + Log.e(TAG, "Battery metadata listener already registered for device."); + } + } + + private void stopListeningToStaleDeviceMetadata() { + if (mMetadataRegisteredDevice == null) return; + try { + mController.removeOnMetadataChangedListener( + mMetadataRegisteredDevice, + mMetadataChangedListener); + mMetadataRegisteredDevice = null; + } catch (IllegalArgumentException e) { + Log.e(TAG, "Battery metadata listener already unregistered for device."); + } + } + private final BluetoothController.Callback mCallback = new BluetoothController.Callback() { @Override public void onBluetoothStateChange(boolean enabled) { @@ -244,4 +305,9 @@ public class BluetoothTile extends QSTileImpl<BooleanState> { refreshState(); } }; + + private final BluetoothAdapter.OnMetadataChangedListener mMetadataChangedListener = + (device, key, value) -> { + if (key == BluetoothDevice.METADATA_MAIN_BATTERY) refreshState(); + }; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothController.java index 0c5b8515071d..3429e25abfc7 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothController.java @@ -16,12 +16,15 @@ package com.android.systemui.statusbar.policy; +import android.bluetooth.BluetoothAdapter; + import com.android.settingslib.bluetooth.CachedBluetoothDevice; import com.android.systemui.Dumpable; import com.android.systemui.statusbar.policy.BluetoothController.Callback; import java.util.Collection; import java.util.List; +import java.util.concurrent.Executor; public interface BluetoothController extends CallbackController<Callback>, Dumpable { boolean isBluetoothSupported(); @@ -44,6 +47,11 @@ public interface BluetoothController extends CallbackController<Callback>, Dumpa int getBondState(CachedBluetoothDevice device); List<CachedBluetoothDevice> getConnectedDevices(); + void addOnMetadataChangedListener(CachedBluetoothDevice device, Executor executor, + BluetoothAdapter.OnMetadataChangedListener listener); + void removeOnMetadataChangedListener(CachedBluetoothDevice device, + BluetoothAdapter.OnMetadataChangedListener listener); + public interface Callback { void onBluetoothStateChange(boolean enabled); void onBluetoothDevicesChanged(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java index acdf0d2bc32b..c804fe76d882 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java @@ -48,6 +48,7 @@ import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.WeakHashMap; +import java.util.concurrent.Executor; import javax.inject.Inject; @@ -78,6 +79,7 @@ public class BluetoothControllerImpl implements BluetoothController, BluetoothCa private final H mHandler; private int mState; + private final BluetoothAdapter mAdapter; /** */ @Inject @@ -88,7 +90,8 @@ public class BluetoothControllerImpl implements BluetoothController, BluetoothCa BluetoothLogger logger, @Background Looper bgLooper, @Main Looper mainLooper, - @Nullable LocalBluetoothManager localBluetoothManager) { + @Nullable LocalBluetoothManager localBluetoothManager, + @Nullable BluetoothAdapter bluetoothAdapter) { mDumpManager = dumpManager; mLogger = logger; mLocalBluetoothManager = localBluetoothManager; @@ -103,6 +106,7 @@ public class BluetoothControllerImpl implements BluetoothController, BluetoothCa mUserManager = (UserManager) context.getSystemService(Context.USER_SERVICE); mCurrentUser = userTracker.getUserId(); mDumpManager.registerDumpable(TAG, this); + mAdapter = bluetoothAdapter; } @Override @@ -412,6 +416,30 @@ public class BluetoothControllerImpl implements BluetoothController, BluetoothCa mHandler.sendEmptyMessage(H.MSG_STATE_CHANGED); } + public void addOnMetadataChangedListener( + @NonNull CachedBluetoothDevice cachedDevice, + Executor executor, + BluetoothAdapter.OnMetadataChangedListener listener + ) { + if (mAdapter == null) return; + mAdapter.addOnMetadataChangedListener( + cachedDevice.getDevice(), + executor, + listener + ); + } + + public void removeOnMetadataChangedListener( + @NonNull CachedBluetoothDevice cachedDevice, + BluetoothAdapter.OnMetadataChangedListener listener + ) { + if (mAdapter == null) return; + mAdapter.removeOnMetadataChangedListener( + cachedDevice.getDevice(), + listener + ); + } + private ActuallyCachedState getCachedState(CachedBluetoothDevice device) { ActuallyCachedState state = mCachedState.get(device); if (state == null) { diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/BluetoothTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/BluetoothTileTest.kt index 75fd0000e0e1..2e77de270c65 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/BluetoothTileTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/BluetoothTileTest.kt @@ -1,6 +1,6 @@ package com.android.systemui.qs.tiles -import android.content.Context +import android.bluetooth.BluetoothDevice import android.os.Handler import android.os.Looper import android.os.UserManager @@ -10,6 +10,8 @@ import android.testing.TestableLooper.RunWithLooper import androidx.test.filters.SmallTest import com.android.internal.logging.MetricsLogger import com.android.internal.logging.testing.UiEventLoggerFake +import com.android.settingslib.Utils +import com.android.settingslib.bluetooth.CachedBluetoothDevice import com.android.systemui.R import com.android.systemui.SysuiTestCase import com.android.systemui.classifier.FalsingManagerFake @@ -21,14 +23,18 @@ import com.android.systemui.qs.QSHost import com.android.systemui.qs.logging.QSLogger import com.android.systemui.qs.tileimpl.QSTileImpl import com.android.systemui.statusbar.policy.BluetoothController +import com.android.systemui.util.mockito.any +import com.android.systemui.util.mockito.eq +import com.android.systemui.util.mockito.mock +import com.android.systemui.util.mockito.whenever import com.google.common.truth.Truth.assertThat import org.junit.After import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import org.mockito.Mock -import org.mockito.Mockito -import org.mockito.Mockito.`when` +import org.mockito.Mockito.times +import org.mockito.Mockito.verify import org.mockito.MockitoAnnotations @RunWith(AndroidTestingRunner::class) @@ -36,21 +42,13 @@ import org.mockito.MockitoAnnotations @SmallTest class BluetoothTileTest : SysuiTestCase() { - @Mock - private lateinit var mockContext: Context - @Mock - private lateinit var qsLogger: QSLogger - @Mock - private lateinit var qsHost: QSHost - @Mock - private lateinit var metricsLogger: MetricsLogger + @Mock private lateinit var qsLogger: QSLogger + @Mock private lateinit var qsHost: QSHost + @Mock private lateinit var metricsLogger: MetricsLogger private val falsingManager = FalsingManagerFake() - @Mock - private lateinit var statusBarStateController: StatusBarStateController - @Mock - private lateinit var activityStarter: ActivityStarter - @Mock - private lateinit var bluetoothController: BluetoothController + @Mock private lateinit var statusBarStateController: StatusBarStateController + @Mock private lateinit var activityStarter: ActivityStarter + @Mock private lateinit var bluetoothController: BluetoothController private val uiEventLogger = UiEventLoggerFake() private lateinit var testableLooper: TestableLooper @@ -61,20 +59,21 @@ class BluetoothTileTest : SysuiTestCase() { MockitoAnnotations.initMocks(this) testableLooper = TestableLooper.get(this) - Mockito.`when`(qsHost.context).thenReturn(mockContext) - Mockito.`when`(qsHost.uiEventLogger).thenReturn(uiEventLogger) + whenever(qsHost.context).thenReturn(mContext) + whenever(qsHost.uiEventLogger).thenReturn(uiEventLogger) - tile = FakeBluetoothTile( - qsHost, - testableLooper.looper, - Handler(testableLooper.looper), - falsingManager, - metricsLogger, - statusBarStateController, - activityStarter, - qsLogger, - bluetoothController - ) + tile = + FakeBluetoothTile( + qsHost, + testableLooper.looper, + Handler(testableLooper.looper), + falsingManager, + metricsLogger, + statusBarStateController, + activityStarter, + qsLogger, + bluetoothController, + ) tile.initialize() testableLooper.processAllMessages() @@ -102,7 +101,7 @@ class BluetoothTileTest : SysuiTestCase() { tile.handleUpdateState(state, /* arg= */ null) assertThat(state.icon) - .isEqualTo(QSTileImpl.ResourceIcon.get(R.drawable.qs_bluetooth_icon_off)) + .isEqualTo(QSTileImpl.ResourceIcon.get(R.drawable.qs_bluetooth_icon_off)) } @Test @@ -114,7 +113,7 @@ class BluetoothTileTest : SysuiTestCase() { tile.handleUpdateState(state, /* arg= */ null) assertThat(state.icon) - .isEqualTo(QSTileImpl.ResourceIcon.get(R.drawable.qs_bluetooth_icon_off)) + .isEqualTo(QSTileImpl.ResourceIcon.get(R.drawable.qs_bluetooth_icon_off)) } @Test @@ -126,7 +125,7 @@ class BluetoothTileTest : SysuiTestCase() { tile.handleUpdateState(state, /* arg= */ null) assertThat(state.icon) - .isEqualTo(QSTileImpl.ResourceIcon.get(R.drawable.qs_bluetooth_icon_on)) + .isEqualTo(QSTileImpl.ResourceIcon.get(R.drawable.qs_bluetooth_icon_on)) } @Test @@ -138,7 +137,76 @@ class BluetoothTileTest : SysuiTestCase() { tile.handleUpdateState(state, /* arg= */ null) assertThat(state.icon) - .isEqualTo(QSTileImpl.ResourceIcon.get(R.drawable.qs_bluetooth_icon_search)) + .isEqualTo(QSTileImpl.ResourceIcon.get(R.drawable.qs_bluetooth_icon_search)) + } + + @Test + fun testSecondaryLabel_whenBatteryMetadataAvailable_isMetadataBatteryLevelState() { + val cachedDevice = mock<CachedBluetoothDevice>() + val state = QSTile.BooleanState() + listenToDeviceMetadata(state, cachedDevice, 50) + + tile.handleUpdateState(state, /* arg= */ null) + + assertThat(state.secondaryLabel) + .isEqualTo( + mContext.getString( + R.string.quick_settings_bluetooth_secondary_label_battery_level, + Utils.formatPercentage(50) + ) + ) + verify(bluetoothController) + .addOnMetadataChangedListener(eq(cachedDevice), any(), any()) + } + + @Test + fun testSecondaryLabel_whenBatteryMetadataUnavailable_isBluetoothBatteryLevelState() { + val state = QSTile.BooleanState() + val cachedDevice = mock<CachedBluetoothDevice>() + listenToDeviceMetadata(state, cachedDevice, 50) + val cachedDevice2 = mock<CachedBluetoothDevice>() + val btDevice = mock<BluetoothDevice>() + whenever(cachedDevice2.device).thenReturn(btDevice) + whenever(btDevice.getMetadata(BluetoothDevice.METADATA_MAIN_BATTERY)).thenReturn(null) + whenever(cachedDevice2.batteryLevel).thenReturn(25) + addConnectedDevice(cachedDevice2) + + tile.handleUpdateState(state, /* arg= */ null) + + assertThat(state.secondaryLabel) + .isEqualTo( + mContext.getString( + R.string.quick_settings_bluetooth_secondary_label_battery_level, + Utils.formatPercentage(25) + ) + ) + verify(bluetoothController, times(1)) + .removeOnMetadataChangedListener(eq(cachedDevice), any()) + } + + @Test + fun testMetadataListener_whenDisconnected_isUnregistered() { + val state = QSTile.BooleanState() + val cachedDevice = mock<CachedBluetoothDevice>() + listenToDeviceMetadata(state, cachedDevice, 50) + disableBluetooth() + + tile.handleUpdateState(state, null) + + verify(bluetoothController, times(1)) + .removeOnMetadataChangedListener(eq(cachedDevice), any()) + } + + @Test + fun testMetadataListener_whenTileNotListening_isUnregistered() { + val state = QSTile.BooleanState() + val cachedDevice = mock<CachedBluetoothDevice>() + listenToDeviceMetadata(state, cachedDevice, 50) + + tile.handleSetListening(false) + + verify(bluetoothController, times(1)) + .removeOnMetadataChangedListener(eq(cachedDevice), any()) } private class FakeBluetoothTile( @@ -150,18 +218,19 @@ class BluetoothTileTest : SysuiTestCase() { statusBarStateController: StatusBarStateController, activityStarter: ActivityStarter, qsLogger: QSLogger, - bluetoothController: BluetoothController - ) : BluetoothTile( - qsHost, - backgroundLooper, - mainHandler, - falsingManager, - metricsLogger, - statusBarStateController, - activityStarter, - qsLogger, - bluetoothController - ) { + bluetoothController: BluetoothController, + ) : + BluetoothTile( + qsHost, + backgroundLooper, + mainHandler, + falsingManager, + metricsLogger, + statusBarStateController, + activityStarter, + qsLogger, + bluetoothController, + ) { var restrictionChecked: String? = null override fun checkIfRestrictionEnforcedByAdminOnly( @@ -173,25 +242,44 @@ class BluetoothTileTest : SysuiTestCase() { } fun enableBluetooth() { - `when`(bluetoothController.isBluetoothEnabled).thenReturn(true) + whenever(bluetoothController.isBluetoothEnabled).thenReturn(true) } fun disableBluetooth() { - `when`(bluetoothController.isBluetoothEnabled).thenReturn(false) + whenever(bluetoothController.isBluetoothEnabled).thenReturn(false) } fun setBluetoothDisconnected() { - `when`(bluetoothController.isBluetoothConnecting).thenReturn(false) - `when`(bluetoothController.isBluetoothConnected).thenReturn(false) + whenever(bluetoothController.isBluetoothConnecting).thenReturn(false) + whenever(bluetoothController.isBluetoothConnected).thenReturn(false) } fun setBluetoothConnected() { - `when`(bluetoothController.isBluetoothConnecting).thenReturn(false) - `when`(bluetoothController.isBluetoothConnected).thenReturn(true) + whenever(bluetoothController.isBluetoothConnecting).thenReturn(false) + whenever(bluetoothController.isBluetoothConnected).thenReturn(true) } fun setBluetoothConnecting() { - `when`(bluetoothController.isBluetoothConnected).thenReturn(false) - `when`(bluetoothController.isBluetoothConnecting).thenReturn(true) + whenever(bluetoothController.isBluetoothConnected).thenReturn(false) + whenever(bluetoothController.isBluetoothConnecting).thenReturn(true) + } + + fun addConnectedDevice(device: CachedBluetoothDevice) { + whenever(bluetoothController.connectedDevices).thenReturn(listOf(device)) + } + + fun listenToDeviceMetadata( + state: QSTile.BooleanState, + cachedDevice: CachedBluetoothDevice, + batteryLevel: Int + ) { + val btDevice = mock<BluetoothDevice>() + whenever(cachedDevice.device).thenReturn(btDevice) + whenever(btDevice.getMetadata(BluetoothDevice.METADATA_MAIN_BATTERY)) + .thenReturn(batteryLevel.toString().toByteArray()) + enableBluetooth() + setBluetoothConnected() + addConnectedDevice(cachedDevice) + tile.handleUpdateState(state, /* arg= */ null) } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BluetoothControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BluetoothControllerImplTest.java index 833cabbaecf4..7d64eaff0845 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BluetoothControllerImplTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BluetoothControllerImplTest.java @@ -21,6 +21,7 @@ import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.reset; +import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -44,6 +45,8 @@ import com.android.systemui.SysuiTestCase; import com.android.systemui.bluetooth.BluetoothLogger; import com.android.systemui.dump.DumpManager; import com.android.systemui.settings.UserTracker; +import com.android.systemui.util.concurrency.FakeExecutor; +import com.android.systemui.util.time.FakeSystemClock; import org.junit.Before; import org.junit.Test; @@ -51,6 +54,7 @@ import org.junit.runner.RunWith; import java.util.ArrayList; import java.util.List; +import java.util.concurrent.Executor; @RunWith(AndroidTestingRunner.class) @RunWithLooper @@ -60,10 +64,11 @@ public class BluetoothControllerImplTest extends SysuiTestCase { private UserTracker mUserTracker; private LocalBluetoothManager mMockBluetoothManager; private CachedBluetoothDeviceManager mMockDeviceManager; - private LocalBluetoothAdapter mMockAdapter; + private LocalBluetoothAdapter mMockLocalAdapter; private TestableLooper mTestableLooper; private DumpManager mMockDumpManager; private BluetoothControllerImpl mBluetoothControllerImpl; + private BluetoothAdapter mMockAdapter; private List<CachedBluetoothDevice> mDevices; @@ -74,10 +79,11 @@ public class BluetoothControllerImplTest extends SysuiTestCase { mDevices = new ArrayList<>(); mUserTracker = mock(UserTracker.class); mMockDeviceManager = mock(CachedBluetoothDeviceManager.class); + mMockAdapter = mock(BluetoothAdapter.class); when(mMockDeviceManager.getCachedDevicesCopy()).thenReturn(mDevices); when(mMockBluetoothManager.getCachedDeviceManager()).thenReturn(mMockDeviceManager); - mMockAdapter = mock(LocalBluetoothAdapter.class); - when(mMockBluetoothManager.getBluetoothAdapter()).thenReturn(mMockAdapter); + mMockLocalAdapter = mock(LocalBluetoothAdapter.class); + when(mMockBluetoothManager.getBluetoothAdapter()).thenReturn(mMockLocalAdapter); when(mMockBluetoothManager.getEventManager()).thenReturn(mock(BluetoothEventManager.class)); when(mMockBluetoothManager.getProfileManager()) .thenReturn(mock(LocalBluetoothProfileManager.class)); @@ -89,7 +95,8 @@ public class BluetoothControllerImplTest extends SysuiTestCase { mock(BluetoothLogger.class), mTestableLooper.getLooper(), mTestableLooper.getLooper(), - mMockBluetoothManager); + mMockBluetoothManager, + mMockAdapter); } @Test @@ -98,7 +105,8 @@ public class BluetoothControllerImplTest extends SysuiTestCase { when(device.isConnected()).thenReturn(true); when(device.getMaxConnectionState()).thenReturn(BluetoothProfile.STATE_CONNECTED); mDevices.add(device); - when(mMockAdapter.getConnectionState()).thenReturn(BluetoothAdapter.STATE_DISCONNECTED); + when(mMockLocalAdapter.getConnectionState()) + .thenReturn(BluetoothAdapter.STATE_DISCONNECTED); mBluetoothControllerImpl.onConnectionStateChanged(null, BluetoothAdapter.STATE_DISCONNECTED); @@ -163,7 +171,7 @@ public class BluetoothControllerImplTest extends SysuiTestCase { @Test public void testOnServiceConnected_updatesConnectionState() { - when(mMockAdapter.getConnectionState()).thenReturn(BluetoothAdapter.STATE_CONNECTING); + when(mMockLocalAdapter.getConnectionState()).thenReturn(BluetoothAdapter.STATE_CONNECTING); mBluetoothControllerImpl.onServiceConnected(); @@ -184,7 +192,7 @@ public class BluetoothControllerImplTest extends SysuiTestCase { @Test public void testOnBluetoothStateChange_updatesConnectionState() { - when(mMockAdapter.getConnectionState()).thenReturn( + when(mMockLocalAdapter.getConnectionState()).thenReturn( BluetoothAdapter.STATE_CONNECTING, BluetoothAdapter.STATE_DISCONNECTED); @@ -240,6 +248,33 @@ public class BluetoothControllerImplTest extends SysuiTestCase { assertTrue(mBluetoothControllerImpl.isBluetoothAudioProfileOnly()); } + @Test + public void testAddOnMetadataChangedListener_registersListenerOnAdapter() { + CachedBluetoothDevice cachedDevice = mock(CachedBluetoothDevice.class); + BluetoothDevice device = mock(BluetoothDevice.class); + when(cachedDevice.getDevice()).thenReturn(device); + Executor executor = new FakeExecutor(new FakeSystemClock()); + BluetoothAdapter.OnMetadataChangedListener listener = (bluetoothDevice, i, bytes) -> { + }; + + mBluetoothControllerImpl.addOnMetadataChangedListener(cachedDevice, executor, listener); + + verify(mMockAdapter, times(1)).addOnMetadataChangedListener(device, executor, listener); + } + + @Test + public void testRemoveOnMetadataChangedListener_removesListenerFromAdapter() { + CachedBluetoothDevice cachedDevice = mock(CachedBluetoothDevice.class); + BluetoothDevice device = mock(BluetoothDevice.class); + when(cachedDevice.getDevice()).thenReturn(device); + BluetoothAdapter.OnMetadataChangedListener listener = (bluetoothDevice, i, bytes) -> { + }; + + mBluetoothControllerImpl.removeOnMetadataChangedListener(cachedDevice, listener); + + verify(mMockAdapter, times(1)).removeOnMetadataChangedListener(device, listener); + } + /** Regression test for b/246876230. */ @Test public void testOnActiveDeviceChanged_null_noCrash() { diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeBluetoothController.java b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeBluetoothController.java index 6cbd175c1084..4025ade5f715 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeBluetoothController.java +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeBluetoothController.java @@ -14,6 +14,7 @@ package com.android.systemui.utils.leaks; +import android.bluetooth.BluetoothAdapter; import android.testing.LeakCheck; import com.android.settingslib.bluetooth.CachedBluetoothDevice; @@ -23,6 +24,7 @@ import com.android.systemui.statusbar.policy.BluetoothController.Callback; import java.util.Collection; import java.util.Collections; import java.util.List; +import java.util.concurrent.Executor; public class FakeBluetoothController extends BaseLeakChecker<Callback> implements BluetoothController { @@ -110,4 +112,16 @@ public class FakeBluetoothController extends BaseLeakChecker<Callback> implement public List<CachedBluetoothDevice> getConnectedDevices() { return Collections.emptyList(); } + + @Override + public void addOnMetadataChangedListener(CachedBluetoothDevice device, Executor executor, + BluetoothAdapter.OnMetadataChangedListener listener) { + + } + + @Override + public void removeOnMetadataChangedListener(CachedBluetoothDevice device, + BluetoothAdapter.OnMetadataChangedListener listener) { + + } } |