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

Commit da5e7e4d authored by Ze Li's avatar Ze Li
Browse files

[Battery refactor] Add battery metadata update mechanism in CachedBluetoothDevice

Test: manual test
Bug: 397847825
Flag: com.android.settingslib.flags.refactor_battery_level_display
Change-Id: I70377b1da2db5fe327cade4011a846952f2cb754
parent de77c38c
Loading
Loading
Loading
Loading
+97 −3
Original line number Diff line number Diff line
@@ -107,7 +107,6 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice>
    private static final int SUMMARY_NO_COLOR_FOR_LOW_BATTERY = 0;

    private final Context mContext;
    private final BluetoothAdapter mLocalAdapter;
    private final LocalBluetoothProfileManager mProfileManager;
    private final Object mProfileLock = new Object();
    BluetoothDevice mDevice;
@@ -115,6 +114,7 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice>
    private int mGroupId;
    private Timestamp mBondTimestamp;
    private LocalBluetoothManager mBluetoothManager;
    private BluetoothAdapter mLocalAdapter;

    // Need this since there is no method for getting RSSI
    short mRssi;
@@ -156,6 +156,7 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice>
    private boolean mIsHeadsetProfileConnectedFail = false;
    private boolean mIsHearingAidProfileConnectedFail = false;
    private boolean mIsLeAudioProfileConnectedFail = false;
    private boolean mIsListeningBatteryChange = false;
    private boolean mUnpairing;
    @Nullable
    private InputDevice mInputDevice;
@@ -193,6 +194,20 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice>
        }
    };

    private final BluetoothAdapter.OnMetadataChangedListener mBatteryMetadataListener =
            (device, key, value) -> {
                if (key == BluetoothDevice.METADATA_MAIN_BATTERY
                        || key == BluetoothDevice.METADATA_UNTETHERED_LEFT_BATTERY
                        || key == BluetoothDevice.METADATA_UNTETHERED_RIGHT_BATTERY
                        || key == BluetoothDevice.METADATA_UNTETHERED_CASE_BATTERY) {
                    Log.d(
                            TAG,
                            "Receiving battery metadata change for device "
                                    + device.getAnonymizedAddress());
                    dispatchAttributesChanged();
                }
            };

    CachedBluetoothDevice(Context context, LocalBluetoothProfileManager profileManager,
            BluetoothDevice device) {
        mContext = context;
@@ -1157,6 +1172,9 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice>
    @Deprecated
    public void registerCallback(Callback callback) {
        mCallbacks.add(callback);
        if (Flags.refactorBatteryLevelDisplay()) {
            registerMainDeviceBatteryMetadataListener();
        }
    }

    /**
@@ -1171,11 +1189,19 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice>
        Objects.requireNonNull(executor, "executor cannot be null");
        Objects.requireNonNull(callback, "callback cannot be null");
        mCallbackExecutorMap.put(callback, executor);
        if (Flags.refactorBatteryLevelDisplay()) {
            registerMainDeviceBatteryMetadataListener();
        }
    }

    public void unregisterCallback(Callback callback) {
        mCallbacks.remove(callback);
        mCallbackExecutorMap.remove(callback);
        if (Flags.refactorBatteryLevelDisplay()
                && mCallbacks.isEmpty()
                && mCallbackExecutorMap.isEmpty()) {
            unregisterMainDeviceBatteryMetadataListener();
        }
    }

    void dispatchAttributesChanged() {
@@ -2317,7 +2343,14 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice>
        final HearingAidInfo tmpHearingAidInfo = mHearingAidInfo;
        // Set main device from sub device
        release();
        if (Flags.refactorBatteryLevelDisplay()) {
            // Unregister the metadata listener on the old main device and register on the new one
            unregisterMainDeviceBatteryMetadataListener();
            mDevice = mSubDevice.mDevice;
            registerMainDeviceBatteryMetadataListener();
        } else {
            mDevice = mSubDevice.mDevice;
        }
        mRssi = mSubDevice.mRssi;
        mJustDiscovered = mSubDevice.mJustDiscovered;
        mHearingAidInfo = mSubDevice.mHearingAidInfo;
@@ -2374,7 +2407,14 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice>

        // Set main device from sub device
        release();
        if (Flags.refactorBatteryLevelDisplay()) {
            // Unregister the metadata listener on the old main device and register on the new one
            unregisterMainDeviceBatteryMetadataListener();
            mDevice = newMainDevice.mDevice;
            registerMainDeviceBatteryMetadataListener();
        } else {
            mDevice = newMainDevice.mDevice;
        }
        mRssi = newMainDevice.mRssi;
        mJustDiscovered = newMainDevice.mJustDiscovered;
        mHearingAidInfo = newMainDevice.mHearingAidInfo;
@@ -2437,6 +2477,11 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice>
        mInputDevice = inputDevice;
    }

    @VisibleForTesting
    void setBluetoothAdapter(BluetoothAdapter bluetoothAdapter) {
        mLocalAdapter = bluetoothAdapter;
    }

    private boolean isAndroidAuto() {
        try {
            ParcelUuid[] uuids = mDevice.getUuids();
@@ -2448,4 +2493,53 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice>
        }
        return false;
    }

    private void registerMainDeviceBatteryMetadataListener() {
        if (mIsListeningBatteryChange) {
            return;
        }
        try {
            // The metadata data changed listener is registered with main thread. If a specific
            // executor is given when registering callback, then the onDeviceAttributesChanged runs
            // on that executor, otherwise on the main executor.
            boolean isSuccess =
                    mLocalAdapter.addOnMetadataChangedListener(
                            mDevice, mContext.getMainExecutor(), mBatteryMetadataListener);
            if (isSuccess) {
                mIsListeningBatteryChange = true;
            } else {
                Log.e(
                        TAG,
                        mDevice.getAnonymizedAddress() + ": add battery metadata listener failed");
            }
        } catch (IllegalArgumentException e) {
            Log.e(
                    TAG,
                    "Metadata listener already registered for device "
                            + mDevice.getAnonymizedAddress());
        }
    }

    private void unregisterMainDeviceBatteryMetadataListener() {
        if (!mIsListeningBatteryChange) {
            return;
        }
        try {
            boolean isSuccess =
                    mLocalAdapter.removeOnMetadataChangedListener(
                            mDevice, mBatteryMetadataListener);
            if (isSuccess) {
                mIsListeningBatteryChange = false;
            } else {
                Log.e(
                        TAG,
                        mDevice.getAnonymizedAddress()
                                + ": remove battery metadata listener failed");
            }
        } catch (IllegalArgumentException e) {
            Log.e(
                    TAG,
                    "No metadata listener registered for device " + mDevice.getAnonymizedAddress());
        }
    }
}
+59 −2
Original line number Diff line number Diff line
@@ -23,6 +23,7 @@ import static com.google.common.truth.Truth.assertThat;

import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
@@ -40,7 +41,6 @@ import android.bluetooth.BluetoothProfile;
import android.bluetooth.BluetoothStatusCodes;
import android.content.Context;
import android.graphics.drawable.BitmapDrawable;
import android.hardware.input.InputManager;
import android.media.AudioManager;
import android.platform.test.annotations.DisableFlags;
import android.platform.test.annotations.EnableFlags;
@@ -54,6 +54,7 @@ import android.view.InputDevice;
import com.android.settingslib.R;
import com.android.settingslib.media.flags.Flags;
import com.android.settingslib.testutils.shadow.ShadowBluetoothAdapter;
import com.android.settingslib.utils.ThreadUtils;
import com.android.settingslib.widget.AdaptiveOutlineDrawable;

import com.google.common.collect.ImmutableList;
@@ -71,6 +72,7 @@ import org.robolectric.shadow.api.Shadow;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Executor;

@RunWith(RobolectricTestRunner.class)
@Config(shadows = {ShadowBluetoothAdapter.class})
@@ -94,6 +96,8 @@ public class CachedBluetoothDeviceTest {
    private static final int LOW_BATTERY_COLOR = android.R.color.holo_red_dark;
    private static final int METADATA_FAST_PAIR_CUSTOMIZED_FIELDS = 25;
    private static final int TEST_DEVICE_ID = 123;
    private final Executor mExecutor = ThreadUtils.getBackgroundExecutor();
    private final CachedBluetoothDevice.Callback mCallback = () -> {};
    private final InputDevice mInputDevice = mock(InputDevice.class);
    @Mock
    private LocalBluetoothProfileManager mProfileManager;
@@ -125,7 +129,7 @@ public class CachedBluetoothDeviceTest {
    @Mock
    private BluetoothLeBroadcastReceiveState mLeBroadcastReceiveState;
    @Mock
    private InputManager mInputManager;
    private BluetoothAdapter mBluetoothAdapter;
    private CachedBluetoothDevice mCachedDevice;
    private CachedBluetoothDevice mSubCachedDevice;
    private AudioManager mAudioManager;
@@ -2562,6 +2566,59 @@ public class CachedBluetoothDeviceTest {
        verify(mDevice).removeBond();
    }

    @Test
    @EnableFlags(FLAG_REFACTOR_BATTERY_LEVEL_DISPLAY)
    public void registerCallback_addOnMetadataChangeListener() {
        mCachedDevice.setBluetoothAdapter(mBluetoothAdapter);
        when(mBluetoothAdapter.addOnMetadataChangedListener(any(), any(), any())).thenReturn(true);

        mCachedDevice.registerCallback(mExecutor, mCallback);

        verify(mBluetoothAdapter).addOnMetadataChangedListener(eq(mDevice), any(), any());
    }

    @Test
    @EnableFlags(FLAG_REFACTOR_BATTERY_LEVEL_DISPLAY)
    public void unregisterCallback_removeOnMetadataChangeListener() {
        mCachedDevice.setBluetoothAdapter(mBluetoothAdapter);
        when(mBluetoothAdapter.addOnMetadataChangedListener(any(), any(), any())).thenReturn(true);

        mCachedDevice.registerCallback(mExecutor, mCallback);
        mCachedDevice.unregisterCallback(mCallback);

        verify(mBluetoothAdapter).removeOnMetadataChangedListener(eq(mDevice), any());
    }

    @Test
    @EnableFlags(FLAG_REFACTOR_BATTERY_LEVEL_DISPLAY)
    public void switchMemberDeviceContent_switchOnMetadataChangeListener() {
        mCachedDevice.addMemberDevice(mSubCachedDevice);
        mCachedDevice.setBluetoothAdapter(mBluetoothAdapter);
        when(mBluetoothAdapter.addOnMetadataChangedListener(any(), any(), any())).thenReturn(true);
        when(mBluetoothAdapter.removeOnMetadataChangedListener(any(), any())).thenReturn(true);

        mCachedDevice.registerCallback(mExecutor, mCallback);
        mCachedDevice.switchMemberDeviceContent(mSubCachedDevice);

        verify(mBluetoothAdapter).removeOnMetadataChangedListener(eq(mDevice), any());
        verify(mBluetoothAdapter).addOnMetadataChangedListener(eq(mSubDevice), any(), any());
    }

    @Test
    @EnableFlags(FLAG_REFACTOR_BATTERY_LEVEL_DISPLAY)
    public void switchSubDeviceContent_switchOnMetadataChangeListener() {
        mCachedDevice.setSubDevice(mSubCachedDevice);
        mCachedDevice.setBluetoothAdapter(mBluetoothAdapter);
        when(mBluetoothAdapter.addOnMetadataChangedListener(any(), any(), any())).thenReturn(true);
        when(mBluetoothAdapter.removeOnMetadataChangedListener(any(), any())).thenReturn(true);

        mCachedDevice.registerCallback(mExecutor, mCallback);
        mCachedDevice.switchSubDeviceContent();

        verify(mBluetoothAdapter).removeOnMetadataChangedListener(eq(mDevice), any());
        verify(mBluetoothAdapter).addOnMetadataChangedListener(eq(mSubDevice), any(), any());
    }

    private void updateProfileStatus(LocalBluetoothProfile profile, int status) {
        doReturn(status).when(profile).getConnectionStatus(mDevice);
        mCachedDevice.onProfileStateChanged(profile, status);