[AudioStream] Hysteresis mode support

Flag: com.android.settingslib.flags.audio_sharing_hysteresis_mode_fix
Test: atest com.android.settings.connecteddevice.audiosharing.audiostreams
Test: manual test with broadcast hysteresis mode
Bug: 355222285
Bug: 355221818
Change-Id: If3a1fbdc391eeda6979868829bc00c435a43c329
This commit is contained in:
Rongxuan Liu
2024-08-29 20:23:48 +00:00
parent c3cfb42524
commit 2c3f54c5e3
16 changed files with 719 additions and 28 deletions

View File

@@ -16,6 +16,8 @@
package com.android.settings.connecteddevice.audiosharing.audiostreams;
import static com.android.settingslib.flags.Flags.FLAG_AUDIO_SHARING_HYSTERESIS_MODE_FIX;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any;
@@ -34,6 +36,7 @@ import android.bluetooth.BluetoothLeBroadcastAssistant;
import android.bluetooth.BluetoothLeBroadcastMetadata;
import android.bluetooth.BluetoothLeBroadcastReceiveState;
import android.content.Context;
import android.platform.test.flag.junit.SetFlagsRule;
import android.view.View;
import androidx.lifecycle.LifecycleOwner;
@@ -72,8 +75,8 @@ import java.util.concurrent.Executor;
ShadowAudioStreamsHelper.class,
})
public class AudioStreamButtonControllerTest {
@Rule public final MockitoRule mMockitoRule = MockitoJUnit.rule();
@Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
private static final String KEY = "audio_stream_button";
private static final int BROADCAST_ID = 1;
private final Context mContext = ApplicationProvider.getApplicationContext();
@@ -83,6 +86,7 @@ public class AudioStreamButtonControllerTest {
@Mock private LocalBluetoothLeBroadcastAssistant mAssistant;
@Mock private AudioStreamsRepository mRepository;
@Mock private ActionButtonsPreference mPreference;
@Mock private BluetoothDevice mSourceDevice;
private Lifecycle mLifecycle;
private LifecycleOwner mLifecycleOwner;
private FakeFeatureFactory mFeatureFactory;
@@ -90,6 +94,7 @@ public class AudioStreamButtonControllerTest {
@Before
public void setUp() {
mSetFlagsRule.disableFlags(FLAG_AUDIO_SHARING_HYSTERESIS_MODE_FIX);
ShadowAudioStreamsHelper.setUseMock(mAudioStreamsHelper);
when(mAudioStreamsHelper.getLeBroadcastAssistant()).thenReturn(mAssistant);
mFeatureFactory = FakeFeatureFactory.setupForTest();
@@ -254,6 +259,33 @@ public class AudioStreamButtonControllerTest {
.setButton1Icon(com.android.settings.R.drawable.ic_settings_close);
}
@Test
public void testCallback_onReceiveStateChangedWithSourcePresent_updateButton() {
mSetFlagsRule.enableFlags(FLAG_AUDIO_SHARING_HYSTERESIS_MODE_FIX);
String address = "11:22:33:44:55:66";
BluetoothLeBroadcastReceiveState state = mock(BluetoothLeBroadcastReceiveState.class);
when(state.getBroadcastId()).thenReturn(BROADCAST_ID);
when(state.getSourceDevice()).thenReturn(mSourceDevice);
when(mSourceDevice.getAddress()).thenReturn(address);
List<Long> bisSyncState = new ArrayList<>();
when(state.getBisSyncState()).thenReturn(bisSyncState);
when(mAudioStreamsHelper.getAllPresentSources()).thenReturn(List.of(state));
mController.displayPreference(mScreen);
mController.mBroadcastAssistantCallback.onReceiveStateChanged(
mock(BluetoothDevice.class), /* sourceId= */ 0, state);
verify(mFeatureFactory.metricsFeatureProvider, never())
.action(any(), eq(SettingsEnums.ACTION_AUDIO_STREAM_JOIN_SUCCEED), anyInt());
// Called twice, once in displayPreference, the other one in callback
verify(mPreference, times(2)).setButton1Enabled(true);
verify(mPreference, times(2)).setButton1Text(R.string.audio_streams_disconnect);
verify(mPreference, times(2))
.setButton1Icon(com.android.settings.R.drawable.ic_settings_close);
}
@Test
public void testCallback_onSourceAddFailed_updateButton() {
when(mAudioStreamsHelper.getAllConnectedSources()).thenReturn(Collections.emptyList());

View File

@@ -18,6 +18,8 @@ package com.android.settings.connecteddevice.audiosharing.audiostreams;
import static com.android.settings.connecteddevice.audiosharing.audiostreams.AudioStreamHeaderController.AUDIO_STREAM_HEADER_LISTENING_NOW_SUMMARY;
import static com.android.settings.connecteddevice.audiosharing.audiostreams.AudioStreamHeaderController.AUDIO_STREAM_HEADER_NOT_LISTENING_SUMMARY;
import static com.android.settings.connecteddevice.audiosharing.audiostreams.AudioStreamHeaderController.AUDIO_STREAM_HEADER_PRESENT_NOW_SUMMARY;
import static com.android.settingslib.flags.Flags.FLAG_AUDIO_SHARING_HYSTERESIS_MODE_FIX;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
@@ -31,6 +33,7 @@ import android.bluetooth.BluetoothLeBroadcastAssistant;
import android.bluetooth.BluetoothLeBroadcastReceiveState;
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.platform.test.flag.junit.SetFlagsRule;
import androidx.lifecycle.LifecycleOwner;
import androidx.preference.PreferenceScreen;
@@ -68,8 +71,9 @@ import java.util.concurrent.Executor;
ShadowAudioStreamsHelper.class,
})
public class AudioStreamHeaderControllerTest {
@Rule public final MockitoRule mMockitoRule = MockitoJUnit.rule();
@Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
private static final String KEY = "audio_stream_header";
private static final int BROADCAST_ID = 1;
private static final String BROADCAST_NAME = "broadcast name";
@@ -81,12 +85,15 @@ public class AudioStreamHeaderControllerTest {
@Mock private AudioStreamDetailsFragment mFragment;
@Mock private LayoutPreference mPreference;
@Mock private EntityHeaderController mHeaderController;
@Mock private BluetoothDevice mBluetoothDevice;
private Lifecycle mLifecycle;
private LifecycleOwner mLifecycleOwner;
private AudioStreamHeaderController mController;
@Before
public void setUp() {
mSetFlagsRule.disableFlags(FLAG_AUDIO_SHARING_HYSTERESIS_MODE_FIX);
ShadowEntityHeaderController.setUseMock(mHeaderController);
ShadowAudioStreamsHelper.setUseMock(mAudioStreamsHelper);
when(mAudioStreamsHelper.getLeBroadcastAssistant()).thenReturn(mAssistant);
@@ -168,6 +175,44 @@ public class AudioStreamHeaderControllerTest {
verify(mScreen).addPreference(any());
}
@Test
public void testDisplayPreference_sourcePresent_setSummary() {
mSetFlagsRule.enableFlags(FLAG_AUDIO_SHARING_HYSTERESIS_MODE_FIX);
String address = "11:22:33:44:55:66";
when(mBroadcastReceiveState.getBroadcastId()).thenReturn(BROADCAST_ID);
when(mBroadcastReceiveState.getSourceDevice()).thenReturn(mBluetoothDevice);
when(mBluetoothDevice.getAddress()).thenReturn(address);
List<Long> bisSyncState = new ArrayList<>();
when(mBroadcastReceiveState.getBisSyncState()).thenReturn(bisSyncState);
when(mAudioStreamsHelper.getAllPresentSources())
.thenReturn(List.of(mBroadcastReceiveState));
mController.displayPreference(mScreen);
verify(mHeaderController).setLabel(BROADCAST_NAME);
verify(mHeaderController).setIcon(any(Drawable.class));
verify(mHeaderController)
.setSummary(mContext.getString(AUDIO_STREAM_HEADER_PRESENT_NOW_SUMMARY));
verify(mHeaderController).done(true);
verify(mScreen).addPreference(any());
}
@Test
public void testDisplayPreference_sourceNotPresent_setSummary() {
mSetFlagsRule.enableFlags(FLAG_AUDIO_SHARING_HYSTERESIS_MODE_FIX);
when(mAudioStreamsHelper.getAllPresentSources()).thenReturn(Collections.emptyList());
mController.displayPreference(mScreen);
verify(mHeaderController).setLabel(BROADCAST_NAME);
verify(mHeaderController).setIcon(any(Drawable.class));
verify(mHeaderController).setSummary(AUDIO_STREAM_HEADER_NOT_LISTENING_SUMMARY);
verify(mHeaderController).done(true);
verify(mScreen).addPreference(any());
}
@Test
public void testCallback_onSourceRemoved_updateButton() {
when(mAudioStreamsHelper.getAllConnectedSources()).thenReturn(Collections.emptyList());
@@ -212,4 +257,25 @@ public class AudioStreamHeaderControllerTest {
.setSummary(mContext.getString(AUDIO_STREAM_HEADER_LISTENING_NOW_SUMMARY));
verify(mHeaderController, times(2)).done(true);
}
@Test
public void testCallback_onReceiveStateChangedWithSourcePresent_updateButton() {
mSetFlagsRule.enableFlags(FLAG_AUDIO_SHARING_HYSTERESIS_MODE_FIX);
String address = "11:22:33:44:55:66";
when(mAudioStreamsHelper.getAllPresentSources())
.thenReturn(List.of(mBroadcastReceiveState));
when(mBroadcastReceiveState.getBroadcastId()).thenReturn(BROADCAST_ID);
when(mBroadcastReceiveState.getSourceDevice()).thenReturn(mBluetoothDevice);
when(mBluetoothDevice.getAddress()).thenReturn(address);
mController.displayPreference(mScreen);
mController.mBroadcastAssistantCallback.onReceiveStateChanged(
mock(BluetoothDevice.class), /* sourceId= */ 0, mBroadcastReceiveState);
// Called twice, once in displayPreference, the other one in callback
verify(mHeaderController, times(2))
.setSummary(mContext.getString(AUDIO_STREAM_HEADER_PRESENT_NOW_SUMMARY));
verify(mHeaderController, times(2)).done(true);
}
}

View File

@@ -16,6 +16,8 @@
package com.android.settings.connecteddevice.audiosharing.audiostreams;
import static com.android.settingslib.flags.Flags.FLAG_AUDIO_SHARING_HYSTERESIS_MODE_FIX;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any;
@@ -30,6 +32,7 @@ import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.content.Context;
import android.platform.test.flag.junit.SetFlagsRule;
import android.text.SpannableString;
import androidx.preference.Preference;
@@ -48,6 +51,8 @@ import org.robolectric.RobolectricTestRunner;
@RunWith(RobolectricTestRunner.class)
public class AudioStreamStateHandlerTest {
@Rule public final MockitoRule mMockitoRule = MockitoJUnit.rule();
@Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
private static final int SUMMARY_RES = 1;
private static final String SUMMARY = "summary";
private final Context mContext = spy(ApplicationProvider.getApplicationContext());
@@ -58,6 +63,7 @@ public class AudioStreamStateHandlerTest {
@Before
public void setUp() {
mSetFlagsRule.disableFlags(FLAG_AUDIO_SHARING_HYSTERESIS_MODE_FIX);
mHandler = spy(new AudioStreamStateHandler());
}
@@ -101,6 +107,28 @@ public class AudioStreamStateHandlerTest {
verify(mPreference).setOnPreferenceClickListener(eq(null));
}
@Test
public void testHandleStateChange_setNewState_sourcePresent() {
mSetFlagsRule.enableFlags(FLAG_AUDIO_SHARING_HYSTERESIS_MODE_FIX);
when(mHandler.getStateEnum())
.thenReturn(AudioStreamsProgressCategoryController.AudioStreamState.SOURCE_PRESENT);
when(mPreference.getAudioStreamState())
.thenReturn(
AudioStreamsProgressCategoryController.AudioStreamState
.ADD_SOURCE_BAD_CODE);
mHandler.handleStateChange(mPreference, mController, mHelper);
verify(mPreference)
.setAudioStreamState(
AudioStreamsProgressCategoryController.AudioStreamState.SOURCE_PRESENT);
verify(mHandler).performAction(any(), any(), any());
verify(mPreference).setIsConnected(eq(true));
verify(mPreference).setSummary(eq(""));
verify(mPreference).setOnPreferenceClickListener(eq(null));
}
@Test
public void testHandleStateChange_setNewState_newSummary_newListener() {
Preference.OnPreferenceClickListener listener =

View File

@@ -19,6 +19,8 @@ package com.android.settings.connecteddevice.audiosharing.audiostreams;
import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
import static com.android.settingslib.flags.Flags.FLAG_AUDIO_SHARING_HYSTERESIS_MODE_FIX;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any;
@@ -37,6 +39,7 @@ import android.bluetooth.BluetoothLeBroadcastReceiveState;
import android.content.Context;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.platform.test.flag.junit.SetFlagsRule;
import androidx.fragment.app.FragmentActivity;
import androidx.test.core.app.ApplicationProvider;
@@ -74,6 +77,8 @@ import java.util.List;
})
public class AudioStreamsHelperTest {
@Rule public final MockitoRule mMockitoRule = MockitoJUnit.rule();
@Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
private static final int GROUP_ID = 1;
private static final int BROADCAST_ID_1 = 1;
private static final int BROADCAST_ID_2 = 2;
@@ -86,10 +91,12 @@ public class AudioStreamsHelperTest {
@Mock private BluetoothLeBroadcastMetadata mMetadata;
@Mock private CachedBluetoothDevice mCachedDevice;
@Mock private BluetoothDevice mDevice;
@Mock private BluetoothDevice mSourceDevice;
private AudioStreamsHelper mHelper;
@Before
public void setUp() {
mSetFlagsRule.disableFlags(FLAG_AUDIO_SHARING_HYSTERESIS_MODE_FIX);
when(mLocalBluetoothManager.getProfileManager()).thenReturn(mLocalBluetoothProfileManager);
when(mLocalBluetoothManager.getCachedDeviceManager()).thenReturn(mDeviceManager);
when(mLocalBluetoothProfileManager.getLeAudioBroadcastAssistantProfile())
@@ -166,6 +173,7 @@ public class AudioStreamsHelperTest {
@Test
public void removeSource_memberHasConnectedSource() {
String address = "11:22:33:44:55:66";
List<BluetoothDevice> devices = new ArrayList<>();
var memberDevice = mock(BluetoothDevice.class);
devices.add(mDevice);
@@ -184,6 +192,8 @@ public class AudioStreamsHelperTest {
List<Long> bisSyncState = new ArrayList<>();
bisSyncState.add(1L);
when(source.getBisSyncState()).thenReturn(bisSyncState);
when(source.getSourceDevice()).thenReturn(mSourceDevice);
when(mSourceDevice.getAddress()).thenReturn(address);
mHelper.removeSource(BROADCAST_ID_2);
@@ -217,6 +227,52 @@ public class AudioStreamsHelperTest {
assertThat(list.get(0)).isEqualTo(source);
}
@Test
public void getAllPresentSources_noSource() {
mSetFlagsRule.enableFlags(FLAG_AUDIO_SHARING_HYSTERESIS_MODE_FIX);
List<BluetoothDevice> devices = new ArrayList<>();
devices.add(mDevice);
String address = "00:00:00:00:00:00";
when(mAssistant.getAllConnectedDevices()).thenReturn(devices);
BluetoothLeBroadcastReceiveState source = mock(BluetoothLeBroadcastReceiveState.class);
when(mDeviceManager.findDevice(any())).thenReturn(mCachedDevice);
when(mCachedDevice.getDevice()).thenReturn(mDevice);
when(mCachedDevice.getGroupId()).thenReturn(GROUP_ID);
when(mAssistant.getAllSources(any())).thenReturn(ImmutableList.of(source));
when(source.getSourceDevice()).thenReturn(mSourceDevice);
when(mSourceDevice.getAddress()).thenReturn(address);
var list = mHelper.getAllPresentSources();
assertThat(list).isEmpty();
}
@Test
public void getAllPresentSources_returnSource() {
mSetFlagsRule.enableFlags(FLAG_AUDIO_SHARING_HYSTERESIS_MODE_FIX);
String address = "11:22:33:44:55:66";
List<BluetoothDevice> devices = new ArrayList<>();
devices.add(mDevice);
when(mAssistant.getAllConnectedDevices()).thenReturn(devices);
BluetoothLeBroadcastReceiveState source = mock(BluetoothLeBroadcastReceiveState.class);
when(mDeviceManager.findDevice(any())).thenReturn(mCachedDevice);
when(mCachedDevice.getDevice()).thenReturn(mDevice);
when(mCachedDevice.getGroupId()).thenReturn(GROUP_ID);
when(mAssistant.getAllSources(any())).thenReturn(ImmutableList.of(source));
when(source.getSourceDevice()).thenReturn(mSourceDevice);
when(mSourceDevice.getAddress()).thenReturn(address);
List<Long> bisSyncState = new ArrayList<>();
when(source.getBisSyncState()).thenReturn(bisSyncState);
var list = mHelper.getAllPresentSources();
assertThat(list).isNotEmpty();
assertThat(list.get(0)).isEqualTo(source);
}
@Test
public void startMediaService_noDevice_doNothing() {
mHelper.startMediaService(mContext, BROADCAST_ID_1, BROADCAST_NAME);

View File

@@ -16,6 +16,8 @@
package com.android.settings.connecteddevice.audiosharing.audiostreams;
import static com.android.settingslib.flags.Flags.FLAG_AUDIO_SHARING_HYSTERESIS_MODE_FIX;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyString;
@@ -25,6 +27,7 @@ import static org.mockito.Mockito.when;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothLeBroadcastMetadata;
import android.bluetooth.BluetoothLeBroadcastReceiveState;
import android.platform.test.flag.junit.SetFlagsRule;
import org.junit.Before;
import org.junit.Rule;
@@ -41,14 +44,18 @@ import java.util.List;
@RunWith(RobolectricTestRunner.class)
public class AudioStreamsProgressCategoryCallbackTest {
@Rule public final MockitoRule mMockitoRule = MockitoJUnit.rule();
@Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
@Mock private AudioStreamsProgressCategoryController mController;
@Mock private BluetoothDevice mDevice;
@Mock private BluetoothLeBroadcastReceiveState mState;
@Mock private BluetoothLeBroadcastMetadata mMetadata;
@Mock private BluetoothDevice mSourceDevice;
private AudioStreamsProgressCategoryCallback mCallback;
@Before
public void setUp() {
mSetFlagsRule.disableFlags(FLAG_AUDIO_SHARING_HYSTERESIS_MODE_FIX);
mCallback = new AudioStreamsProgressCategoryCallback(mController);
}
@@ -62,6 +69,20 @@ public class AudioStreamsProgressCategoryCallbackTest {
verify(mController).handleSourceConnected(any());
}
@Test
public void testOnReceiveStateChanged_sourcePresent() {
mSetFlagsRule.enableFlags(FLAG_AUDIO_SHARING_HYSTERESIS_MODE_FIX);
String address = "11:22:33:44:55:66";
List<Long> bisSyncState = new ArrayList<>();
when(mState.getBisSyncState()).thenReturn(bisSyncState);
when(mState.getSourceDevice()).thenReturn(mSourceDevice);
when(mSourceDevice.getAddress()).thenReturn(address);
mCallback.onReceiveStateChanged(mDevice, /* sourceId= */ 0, mState);
verify(mController).handleSourcePresent(any());
}
@Test
public void testOnReceiveStateChanged_badCode() {
when(mState.getPaSyncState())

View File

@@ -20,10 +20,12 @@ import static com.android.settings.connecteddevice.audiosharing.audiostreams.Aud
import static com.android.settings.connecteddevice.audiosharing.audiostreams.AudioStreamsProgressCategoryController.AudioStreamState.ADD_SOURCE_FAILED;
import static com.android.settings.connecteddevice.audiosharing.audiostreams.AudioStreamsProgressCategoryController.AudioStreamState.ADD_SOURCE_WAIT_FOR_RESPONSE;
import static com.android.settings.connecteddevice.audiosharing.audiostreams.AudioStreamsProgressCategoryController.AudioStreamState.SOURCE_ADDED;
import static com.android.settings.connecteddevice.audiosharing.audiostreams.AudioStreamsProgressCategoryController.AudioStreamState.SOURCE_PRESENT;
import static com.android.settings.connecteddevice.audiosharing.audiostreams.AudioStreamsProgressCategoryController.AudioStreamState.SYNCED;
import static com.android.settings.connecteddevice.audiosharing.audiostreams.AudioStreamsProgressCategoryController.AudioStreamState.WAIT_FOR_SYNC;
import static com.android.settings.connecteddevice.audiosharing.audiostreams.AudioStreamsProgressCategoryController.UNSET_BROADCAST_ID;
import static com.android.settings.core.BasePreferenceController.AVAILABLE;
import static com.android.settingslib.flags.Flags.FLAG_AUDIO_SHARING_HYSTERESIS_MODE_FIX;
import static com.google.common.truth.Truth.assertThat;
@@ -41,12 +43,14 @@ import static org.robolectric.Shadows.shadowOf;
import static java.util.Collections.emptyList;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothLeAudioContentMetadata;
import android.bluetooth.BluetoothLeBroadcastMetadata;
import android.bluetooth.BluetoothLeBroadcastReceiveState;
import android.bluetooth.BluetoothProfile;
import android.content.Context;
import android.os.Looper;
import android.platform.test.flag.junit.SetFlagsRule;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
@@ -96,6 +100,8 @@ import java.util.List;
})
public class AudioStreamsProgressCategoryControllerTest {
@Rule public final MockitoRule mMockitoRule = MockitoJUnit.rule();
@Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
private static final String VALID_METADATA =
"BLUETOOTH:UUID:184F;BN:VGVzdA==;AT:1;AD:00A1A1A1A1A1;BI:1E240;BC:VGVzdENvZGU=;"
+ "MD:BgNwVGVzdA==;AS:1;PI:A0;NS:1;BS:3;NB:2;SM:BQNUZXN0BARlbmc=;;";
@@ -115,6 +121,7 @@ public class AudioStreamsProgressCategoryControllerTest {
@Mock private BluetoothLeBroadcastMetadata mMetadata;
@Mock private CachedBluetoothDevice mDevice;
@Mock private AudioStreamsProgressCategoryPreference mPreference;
@Mock private BluetoothDevice mSourceDevice;
private Lifecycle mLifecycle;
private LifecycleOwner mLifecycleOwner;
private Fragment mFragment;
@@ -125,6 +132,7 @@ public class AudioStreamsProgressCategoryControllerTest {
ShadowAudioStreamsHelper.setUseMock(mAudioStreamsHelper);
when(mAudioStreamsHelper.getLeBroadcastAssistant()).thenReturn(mLeBroadcastAssistant);
when(mAudioStreamsHelper.getAllConnectedSources()).thenReturn(emptyList());
mSetFlagsRule.disableFlags(FLAG_AUDIO_SHARING_HYSTERESIS_MODE_FIX);
ShadowBluetoothUtils.sLocalBluetoothManager = mLocalBtManager;
when(mLocalBtManager.getEventManager()).thenReturn(mBluetoothEventManager);
@@ -282,6 +290,29 @@ public class AudioStreamsProgressCategoryControllerTest {
verify(mController, never()).moveToState(any(), any());
}
@Test
public void testOnStart_initHasDevice_getPresentSources() {
mSetFlagsRule.enableFlags(FLAG_AUDIO_SHARING_HYSTERESIS_MODE_FIX);
// Setup a device
ShadowAudioStreamsHelper.setCachedBluetoothDeviceInSharingOrLeConnected(mDevice);
List<BluetoothLeBroadcastReceiveState> connectedList = new ArrayList<>();
// Empty connected device list
when(mAudioStreamsHelper.getAllConnectedSources()).thenReturn(connectedList);
mController.onStart(mLifecycleOwner);
shadowOf(Looper.getMainLooper()).idle();
verify(mAudioStreamsHelper).getAllPresentSources();
verify(mLeBroadcastAssistant).startSearchingForSources(any());
var dialog = ShadowAlertDialog.getLatestAlertDialog();
assertThat(dialog).isNull();
verify(mController, never()).moveToState(any(), any());
}
@Test
public void testOnStart_handleSourceFromQrCode() {
// Setup a device
@@ -764,6 +795,58 @@ public class AudioStreamsProgressCategoryControllerTest {
assertThat(states.get(1)).isEqualTo(ADD_SOURCE_FAILED);
}
@Test
public void testHandleSourcePresent_updateState() {
mSetFlagsRule.enableFlags(FLAG_AUDIO_SHARING_HYSTERESIS_MODE_FIX);
String address = "11:22:33:44:55:66";
// Setup a device
ShadowAudioStreamsHelper.setCachedBluetoothDeviceInSharingOrLeConnected(mDevice);
// Setup mPreference so it's not null
mController.displayPreference(mScreen);
// A new source found
when(mMetadata.getBroadcastId()).thenReturn(NEWLY_FOUND_BROADCAST_ID);
mController.handleSourceFound(mMetadata);
shadowOf(Looper.getMainLooper()).idle();
// The connected source is identified as having a bad code
BluetoothLeBroadcastReceiveState receiveState =
mock(BluetoothLeBroadcastReceiveState.class);
when(receiveState.getBroadcastId()).thenReturn(NEWLY_FOUND_BROADCAST_ID);
when(receiveState.getSourceDevice()).thenReturn(mSourceDevice);
when(mSourceDevice.getAddress()).thenReturn(address);
List<Long> bisSyncState = new ArrayList<>();
when(receiveState.getBisSyncState()).thenReturn(bisSyncState);
// The new found source is identified as failed to connect
mController.handleSourcePresent(receiveState);
shadowOf(Looper.getMainLooper()).idle();
ArgumentCaptor<AudioStreamPreference> preference =
ArgumentCaptor.forClass(AudioStreamPreference.class);
ArgumentCaptor<AudioStreamsProgressCategoryController.AudioStreamState> state =
ArgumentCaptor.forClass(
AudioStreamsProgressCategoryController.AudioStreamState.class);
verify(mController, times(2)).moveToState(preference.capture(), state.capture());
List<AudioStreamPreference> preferences = preference.getAllValues();
assertThat(preferences.size()).isEqualTo(2);
List<AudioStreamsProgressCategoryController.AudioStreamState> states = state.getAllValues();
assertThat(states.size()).isEqualTo(2);
// Verify one preference is created with SYNCED
assertThat(preferences.get(0).getAudioStreamBroadcastId())
.isEqualTo(NEWLY_FOUND_BROADCAST_ID);
assertThat(states.get(0)).isEqualTo(SYNCED);
// Verify the preference is updated to state ADD_SOURCE_FAILED
assertThat(preferences.get(1).getAudioStreamBroadcastId())
.isEqualTo(NEWLY_FOUND_BROADCAST_ID);
assertThat(states.get(1)).isEqualTo(SOURCE_PRESENT);
}
private static BluetoothLeBroadcastReceiveState createConnectedMock(int id) {
var connected = mock(BluetoothLeBroadcastReceiveState.class);
List<Long> bisSyncState = new ArrayList<>();

View File

@@ -0,0 +1,137 @@
/*
* Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settings.connecteddevice.audiosharing.audiostreams;
import static android.app.settings.SettingsEnums.AUDIO_STREAM_MAIN;
import static com.android.settings.connecteddevice.audiosharing.audiostreams.SourcePresentState.AUDIO_STREAM_SOURCE_PRESENT_STATE_SUMMARY;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import androidx.fragment.app.FragmentActivity;
import androidx.preference.Preference;
import androidx.test.core.app.ApplicationProvider;
import com.android.settings.R;
import com.android.settings.SettingsActivity;
import com.android.settings.testutils.FakeFeatureFactory;
import com.android.settings.testutils.shadow.ShadowFragment;
import com.android.settingslib.core.instrumentation.MetricsFeatureProvider;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.annotation.Config;
@RunWith(RobolectricTestRunner.class)
@Config(
shadows = {
ShadowFragment.class,
})
public class SourcePresentStateTest {
@Rule public final MockitoRule mMockitoRule = MockitoJUnit.rule();
private static final int BROADCAST_ID = 1;
private static final String BROADCAST_TITLE = "title";
private final Context mContext = ApplicationProvider.getApplicationContext();
@Mock private AudioStreamPreference mPreference;
@Mock private AudioStreamsProgressCategoryController mController;
@Mock private AudioStreamsHelper mHelper;
@Mock private AudioStreamsRepository mRepository;
@Mock private AudioStreamsDashboardFragment mFragment;
@Mock private FragmentActivity mActivity;
private FakeFeatureFactory mFeatureFactory;
private SourcePresentState mInstance;
@Before
public void setUp() {
when(mFragment.getActivity()).thenReturn(mActivity);
mFeatureFactory = FakeFeatureFactory.setupForTest();
mInstance = new SourcePresentState();
when(mPreference.getAudioStreamBroadcastId()).thenReturn(BROADCAST_ID);
when(mPreference.getTitle()).thenReturn(BROADCAST_TITLE);
}
@Test
public void testGetInstance() {
mInstance = SourcePresentState.getInstance();
assertThat(mInstance).isNotNull();
assertThat(mInstance).isInstanceOf(SourcePresentState.class);
}
@Test
public void testGetSummary() {
int summary = mInstance.getSummary();
assertThat(summary).isEqualTo(AUDIO_STREAM_SOURCE_PRESENT_STATE_SUMMARY);
}
@Test
public void testGetStateEnum() {
AudioStreamsProgressCategoryController.AudioStreamState stateEnum =
mInstance.getStateEnum();
assertThat(stateEnum)
.isEqualTo(AudioStreamsProgressCategoryController.AudioStreamState.SOURCE_PRESENT);
}
@Test
public void testGetOnClickListener_startSubSettings() {
when(mController.getFragment()).thenReturn(mFragment);
when(mFragment.getMetricsCategory()).thenReturn(AUDIO_STREAM_MAIN);
Preference.OnPreferenceClickListener listener = mInstance.getOnClickListener(mController);
assertThat(listener).isNotNull();
// mContext is not an Activity context, calling startActivity() from outside of an Activity
// context requires the FLAG_ACTIVITY_NEW_TASK flag, create a mock to avoid this
// AndroidRuntimeException.
Context activityContext = mock(Context.class);
when(mPreference.getContext()).thenReturn(activityContext);
listener.onPreferenceClick(mPreference);
ArgumentCaptor<Intent> argumentCaptor = ArgumentCaptor.forClass(Intent.class);
verify(activityContext).startActivity(argumentCaptor.capture());
Intent intent = argumentCaptor.getValue();
assertThat(intent.getStringExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT))
.isEqualTo(AudioStreamDetailsFragment.class.getName());
assertThat(intent.getIntExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT_TITLE_RESID, 0))
.isEqualTo(R.string.audio_streams_detail_page_title);
assertThat(intent.getIntExtra(MetricsFeatureProvider.EXTRA_SOURCE_METRICS_CATEGORY, 0))
.isEqualTo(AUDIO_STREAM_MAIN);
Bundle bundle = intent.getBundleExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT_ARGUMENTS);
assertThat(bundle).isNotNull();
assertThat(bundle.getString(AudioStreamDetailsFragment.BROADCAST_NAME_ARG))
.isEqualTo(BROADCAST_TITLE);
assertThat(bundle.getInt(AudioStreamDetailsFragment.BROADCAST_ID_ARG))
.isEqualTo(BROADCAST_ID);
}
}

View File

@@ -59,6 +59,11 @@ public class ShadowAudioStreamsHelper {
return sMockHelper.getAllConnectedSources();
}
@Implementation
public List<BluetoothLeBroadcastReceiveState> getAllPresentSources() {
return sMockHelper.getAllPresentSources();
}
/** Gets {@link CachedBluetoothDevice} in sharing or le connected */
@Implementation
public static Optional<CachedBluetoothDevice> getCachedBluetoothDeviceInSharingOrLeConnected(