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

Commit b56b9cbe authored by Ze Li's avatar Ze Li Committed by Android (Google) Code Review
Browse files

Merge "Fix the bug the qs tile secondary title is using the wrong battery source." into main

parents 7d2822c1 f5497f7f
Loading
Loading
Loading
Loading
+51 −10
Original line number Diff line number Diff line
@@ -16,6 +16,7 @@

package com.android.systemui.qs.tiles;

import static com.android.settingslib.flags.Flags.refactorBatteryLevelDisplay;
import static com.android.settingslib.satellite.SatelliteDialogUtils.TYPE_IS_BLUETOOTH;
import static com.android.systemui.Flags.iconRefresh2025;
import static com.android.systemui.util.PluralMessageFormaterKt.icuMessageFormat;
@@ -40,6 +41,7 @@ import androidx.annotation.VisibleForTesting;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.settingslib.Utils;
import com.android.settingslib.bluetooth.BatteryLevelsInfo;
import com.android.settingslib.bluetooth.BluetoothUtils;
import com.android.settingslib.bluetooth.CachedBluetoothDevice;
import com.android.settingslib.satellite.SatelliteDialogUtils;
@@ -85,6 +87,7 @@ public class BluetoothTile extends QSTileImpl<BooleanState> {
    private final BluetoothController mController;

    private CachedBluetoothDevice mMetadataRegisteredDevice = null;
    private CachedBluetoothDevice mBatteryCallbackRegisteredDevice = null;

    private final Executor mExecutor;

@@ -190,9 +193,13 @@ public class BluetoothTile extends QSTileImpl<BooleanState> {
        super.handleSetListening(listening);

        if (!listening) {
            if (refactorBatteryLevelDisplay()) {
                unregisterBatteryChangedCallback();
            } else {
                stopListeningToStaleDeviceMetadata();
            }
        }
    }

    @Override
    protected void handleUpdateState(BooleanState state, Object arg) {
@@ -211,8 +218,12 @@ public class BluetoothTile extends QSTileImpl<BooleanState> {
                    || mController.getBluetoothState() == BluetoothAdapter.STATE_TURNING_OFF;
        }
        if (!enabled || !connected || state.isTransient) {
            if (refactorBatteryLevelDisplay()) {
                unregisterBatteryChangedCallback();
            } else {
                stopListeningToStaleDeviceMetadata();
            }
        }
        state.dualTarget = true;
        state.value = enabled;
        state.label = mContext.getString(R.string.quick_settings_bluetooth_label);
@@ -285,7 +296,11 @@ public class BluetoothTile extends QSTileImpl<BooleanState> {
        List<CachedBluetoothDevice> connectedDevices = mController.getConnectedDevices();
        if (enabled && connected && !connectedDevices.isEmpty()) {
            if (connectedDevices.size() > 1) {
                if (refactorBatteryLevelDisplay()) {
                    unregisterBatteryChangedCallback();
                } else {
                    stopListeningToStaleDeviceMetadata();
                }
                return icuMessageFormat(mContext.getResources(),
                        R.string.quick_settings_hotspot_secondary_label_num_devices,
                        connectedDevices.size());
@@ -293,15 +308,26 @@ public class BluetoothTile extends QSTileImpl<BooleanState> {

            CachedBluetoothDevice device = connectedDevices.get(0);

            int batteryLevel = BluetoothDevice.BATTERY_LEVEL_UNKNOWN;
            if (refactorBatteryLevelDisplay()) {
                BatteryLevelsInfo batteryLevelsInfo = device.getBatteryLevelsInfo();
                if (batteryLevelsInfo != null) {
                    batteryLevel = batteryLevelsInfo.getOverallBatteryLevel();
                    registerBatteryChangedCallback(device);
                } else {
                    unregisterBatteryChangedCallback();
                }
            } else {
                // Use battery level provided by FastPair metadata if available.
                // If not, fallback to the default battery level from bluetooth.
            int batteryLevel = getMetadataBatteryLevel(device);
                batteryLevel = getMetadataBatteryLevel(device);
                if (batteryLevel > BluetoothUtils.META_INT_ERROR) {
                    listenToMetadata(device);
                } else {
                    stopListeningToStaleDeviceMetadata();
                    batteryLevel = device.getMinBatteryLevelWithMemberDevices();
                }
            }

            if (batteryLevel > BluetoothDevice.BATTERY_LEVEL_UNKNOWN) {
                return mContext.getString(
@@ -370,6 +396,19 @@ public class BluetoothTile extends QSTileImpl<BooleanState> {
        }
    }

    private void registerBatteryChangedCallback(CachedBluetoothDevice cachedDevice) {
        if (cachedDevice.equals(mBatteryCallbackRegisteredDevice)) return;
        unregisterBatteryChangedCallback();
        cachedDevice.registerCallback(mExecutor, mBatteryChangedCallback);
        mBatteryCallbackRegisteredDevice = cachedDevice;
    }

    private void unregisterBatteryChangedCallback() {
        if (mBatteryCallbackRegisteredDevice == null) return;
        mBatteryCallbackRegisteredDevice.unregisterCallback(mBatteryChangedCallback);
        mBatteryCallbackRegisteredDevice = null;
    }

    private final BluetoothController.Callback mCallback = new BluetoothController.Callback() {
        @Override
        public void onBluetoothStateChange(boolean enabled) {
@@ -386,4 +425,6 @@ public class BluetoothTile extends QSTileImpl<BooleanState> {
            (device, key, value) -> {
                if (key == BluetoothDevice.METADATA_MAIN_BATTERY) refreshState();
            };

    private final CachedBluetoothDevice.Callback mBatteryChangedCallback = this::refreshState;
}
+80 −3
Original line number Diff line number Diff line
@@ -18,7 +18,9 @@ import android.testing.TestableLooper.RunWithLooper
import androidx.test.filters.SmallTest
import com.android.internal.logging.MetricsLogger
import com.android.settingslib.Utils
import com.android.settingslib.bluetooth.BatteryLevelsInfo
import com.android.settingslib.bluetooth.CachedBluetoothDevice
import com.android.settingslib.flags.Flags.FLAG_REFACTOR_BATTERY_LEVEL_DISPLAY
import com.android.systemui.Flags
import com.android.systemui.SysuiTestCase
import com.android.systemui.bluetooth.ui.viewModel.BluetoothDetailsContentViewModel
@@ -39,10 +41,7 @@ import com.android.systemui.qs.tileimpl.QSTileImpl
import com.android.systemui.qs.tileimpl.QSTileImpl.DrawableIconWithRes
import com.android.systemui.res.R
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 dagger.Lazy
import kotlinx.coroutines.Job
@@ -56,6 +55,9 @@ import org.mockito.Mockito.times
import org.mockito.Mockito.verify
import org.mockito.Mockito.`when`
import org.mockito.MockitoAnnotations
import org.mockito.kotlin.any
import org.mockito.kotlin.mock
import org.mockito.kotlin.whenever
import platform.test.runner.parameterized.ParameterizedAndroidJunit4
import platform.test.runner.parameterized.Parameters

@@ -185,6 +187,7 @@ class BluetoothTileTest(flags: FlagsParameterization) : SysuiTestCase() {
    }

    @Test
    @DisableFlags(FLAG_REFACTOR_BATTERY_LEVEL_DISPLAY)
    fun testSecondaryLabel_whenBatteryMetadataAvailable_isMetadataBatteryLevelState() {
        val cachedDevice = mock<CachedBluetoothDevice>()
        val state = QSTile.BooleanState()
@@ -203,6 +206,7 @@ class BluetoothTileTest(flags: FlagsParameterization) : SysuiTestCase() {
    }

    @Test
    @DisableFlags(FLAG_REFACTOR_BATTERY_LEVEL_DISPLAY)
    fun testSecondaryLabel_whenBatteryMetadataUnavailable_isBluetoothBatteryLevelState() {
        val state = QSTile.BooleanState()
        val cachedDevice = mock<CachedBluetoothDevice>()
@@ -227,6 +231,37 @@ class BluetoothTileTest(flags: FlagsParameterization) : SysuiTestCase() {
            .removeOnMetadataChangedListener(eq(cachedDevice), any())
    }

    @Test
    @EnableFlags(FLAG_REFACTOR_BATTERY_LEVEL_DISPLAY)
    fun testSecondaryLabel_whenBatteryLevelsInfoAvailable_showBatteryLevelAndRegisterCallback() {
        val cachedDevice = mock<CachedBluetoothDevice>()
        val state = QSTile.BooleanState()
        listenToDeviceBatteryLevelsInfo(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(cachedDevice).registerCallback(any(), any())
    }

    @Test
    @EnableFlags(FLAG_REFACTOR_BATTERY_LEVEL_DISPLAY)
    fun testSecondaryLabel_whenBatteryLevelsInfoUnavailable_noBatteryLevel() {
        val state = QSTile.BooleanState()
        val cachedDevice = mock<CachedBluetoothDevice>()
        listenToDeviceBatteryLevelsInfo(state, cachedDevice, -1)

        tile.handleUpdateState(state, /* arg= */ null)

        assertThat(state.secondaryLabel).isEqualTo("")
    }

    @Test
    @DisableFlags(QsDetailedView.FLAG_NAME)
    fun handleClick_hasSatelliteFeatureButNoQsTileDialogAndClickIsProcessing_doNothing() {
@@ -256,6 +291,7 @@ class BluetoothTileTest(flags: FlagsParameterization) : SysuiTestCase() {
    }

    @Test
    @DisableFlags(FLAG_REFACTOR_BATTERY_LEVEL_DISPLAY)
    fun testMetadataListener_whenDisconnected_isUnregistered() {
        val state = QSTile.BooleanState()
        val cachedDevice = mock<CachedBluetoothDevice>()
@@ -269,6 +305,7 @@ class BluetoothTileTest(flags: FlagsParameterization) : SysuiTestCase() {
    }

    @Test
    @DisableFlags(FLAG_REFACTOR_BATTERY_LEVEL_DISPLAY)
    fun testMetadataListener_whenTileNotListening_isUnregistered() {
        val state = QSTile.BooleanState()
        val cachedDevice = mock<CachedBluetoothDevice>()
@@ -280,6 +317,31 @@ class BluetoothTileTest(flags: FlagsParameterization) : SysuiTestCase() {
            .removeOnMetadataChangedListener(eq(cachedDevice), any())
    }

    @Test
    @EnableFlags(FLAG_REFACTOR_BATTERY_LEVEL_DISPLAY)
    fun testCallbackRegister_whenDisconnected_isUnregistered() {
        val state = QSTile.BooleanState()
        val cachedDevice = mock<CachedBluetoothDevice>()
        listenToDeviceBatteryLevelsInfo(state, cachedDevice, 50)
        disableBluetooth()

        tile.handleUpdateState(state, null)

        verify(cachedDevice, times(1)).unregisterCallback(any())
    }

    @Test
    @EnableFlags(FLAG_REFACTOR_BATTERY_LEVEL_DISPLAY)
    fun testCallbackRegister_whenTileNotListening_isUnregistered() {
        val state = QSTile.BooleanState()
        val cachedDevice = mock<CachedBluetoothDevice>()
        listenToDeviceBatteryLevelsInfo(state, cachedDevice, 50)

        tile.handleSetListening(false)

        verify(cachedDevice, times(1)).unregisterCallback(any())
    }

    @Test
    @EnableFlags(QSComposeFragment.FLAG_NAME)
    fun disableBluetooth_transientTurningOff() {
@@ -399,6 +461,21 @@ class BluetoothTileTest(flags: FlagsParameterization) : SysuiTestCase() {
        tile.handleUpdateState(state, /* arg= */ null)
    }

    private fun listenToDeviceBatteryLevelsInfo(
        state: QSTile.BooleanState,
        cachedDevice: CachedBluetoothDevice,
        batteryLevel: Int,
    ) {
        val btDevice = mock<BluetoothDevice>()
        whenever(cachedDevice.device).thenReturn(btDevice)
        whenever(cachedDevice.batteryLevelsInfo)
            .thenReturn(BatteryLevelsInfo(batteryLevel, batteryLevel, batteryLevel, batteryLevel))
        enableBluetooth()
        setBluetoothConnected()
        addConnectedDevice(cachedDevice)
        tile.handleUpdateState(state, /* arg= */ null)
    }

    private fun createExpectedIcon(resId: Int): QSTile.Icon {
        return if (isEnabled) {
            DrawableIconWithRes(mContext.getDrawable(resId), resId)