summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Vania Januar <vanjan@google.com> 2023-03-22 15:37:04 +0000
committer Android (Google) Code Review <android-gerrit@google.com> 2023-03-22 15:37:04 +0000
commitb9a7b803ef1c2034cd9e8a8bf9384f8188f36b25 (patch)
tree84c7070a9ea33b2a90b1c0b94c2484a16faebd03
parent300f56c8400bd1f2d539d3a9cf6e4982204b8d78 (diff)
parent50bbe7cd1e86e745b3c97aafb543be435f464160 (diff)
Merge "Use FastPair battery level in blueooth QS tile." into udc-dev
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java80
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothController.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java30
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/tiles/BluetoothTileTest.kt196
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BluetoothControllerImplTest.java49
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeBluetoothController.java14
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) {
+
+ }
}