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

Commit b9a7b803 authored by Vania Januar's avatar Vania Januar Committed by Android (Google) Code Review
Browse files

Merge "Use FastPair battery level in blueooth QS tile." into udc-dev

parents 300f56c8 50bbe7cd
Loading
Loading
Loading
Loading
+73 −7
Original line number Diff line number Diff line
@@ -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
@@ -116,6 +126,15 @@ public class BluetoothTile extends QSTileImpl<BooleanState> {
        return mContext.getString(R.string.quick_settings_bluetooth_label);
    }

    @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);
@@ -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();
            };
}
+8 −0
Original line number Diff line number Diff line
@@ -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();
+29 −1
Original line number Diff line number Diff line
@@ -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) {
+142 −54
Original line number Diff line number Diff line
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,10 +59,11 @@ 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(
        tile =
            FakeBluetoothTile(
                qsHost,
                testableLooper.looper,
                Handler(testableLooper.looper),
@@ -73,7 +72,7 @@ class BluetoothTileTest : SysuiTestCase() {
                statusBarStateController,
                activityStarter,
                qsLogger,
            bluetoothController
                bluetoothController,
            )

        tile.initialize()
@@ -141,6 +140,75 @@ class BluetoothTileTest : SysuiTestCase() {
            .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(
        qsHost: QSHost,
        backgroundLooper: Looper,
@@ -150,8 +218,9 @@ class BluetoothTileTest : SysuiTestCase() {
        statusBarStateController: StatusBarStateController,
        activityStarter: ActivityStarter,
        qsLogger: QSLogger,
        bluetoothController: BluetoothController
    ) : BluetoothTile(
        bluetoothController: BluetoothController,
    ) :
        BluetoothTile(
            qsHost,
            backgroundLooper,
            mainHandler,
@@ -160,7 +229,7 @@ class BluetoothTileTest : SysuiTestCase() {
            statusBarStateController,
            activityStarter,
            qsLogger,
        bluetoothController
            bluetoothController,
        ) {
        var restrictionChecked: String? = null

@@ -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)
    }
}
+42 −7
Original line number Diff line number Diff line
@@ -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() {
Loading