Loading packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java +97 −3 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading Loading @@ -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; Loading Loading @@ -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; Loading Loading @@ -1157,6 +1172,9 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice> @Deprecated public void registerCallback(Callback callback) { mCallbacks.add(callback); if (Flags.refactorBatteryLevelDisplay()) { registerMainDeviceBatteryMetadataListener(); } } /** Loading @@ -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() { Loading Loading @@ -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; Loading Loading @@ -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; Loading Loading @@ -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(); Loading @@ -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()); } } } packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java +59 −2 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading @@ -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; Loading @@ -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}) Loading @@ -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; Loading Loading @@ -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; Loading Loading @@ -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); Loading Loading
packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java +97 −3 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading Loading @@ -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; Loading Loading @@ -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; Loading Loading @@ -1157,6 +1172,9 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice> @Deprecated public void registerCallback(Callback callback) { mCallbacks.add(callback); if (Flags.refactorBatteryLevelDisplay()) { registerMainDeviceBatteryMetadataListener(); } } /** Loading @@ -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() { Loading Loading @@ -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; Loading Loading @@ -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; Loading Loading @@ -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(); Loading @@ -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()); } } }
packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java +59 −2 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading @@ -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; Loading @@ -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}) Loading @@ -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; Loading Loading @@ -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; Loading Loading @@ -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); Loading