[Audiosharing] Created test for the main controller.
Test: atest -c com.android.settings.connecteddevice.audiosharing.audiostreams Flag: com.android.settingslib.flags.enable_le_audio_qr_code_private_broadcast_sharing Bug: 345686602 Change-Id: Ieb735d392607c131c213be90cd72c4b7a9ed958d
This commit is contained in:
@@ -20,6 +20,7 @@ import static java.util.Collections.emptyList;
|
|||||||
|
|
||||||
import android.app.AlertDialog;
|
import android.app.AlertDialog;
|
||||||
import android.app.settings.SettingsEnums;
|
import android.app.settings.SettingsEnums;
|
||||||
|
import android.bluetooth.BluetoothAdapter;
|
||||||
import android.bluetooth.BluetoothLeBroadcastMetadata;
|
import android.bluetooth.BluetoothLeBroadcastMetadata;
|
||||||
import android.bluetooth.BluetoothLeBroadcastReceiveState;
|
import android.bluetooth.BluetoothLeBroadcastReceiveState;
|
||||||
import android.bluetooth.BluetoothProfile;
|
import android.bluetooth.BluetoothProfile;
|
||||||
@@ -27,10 +28,12 @@ import android.content.Context;
|
|||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.fragment.app.Fragment;
|
||||||
import androidx.lifecycle.DefaultLifecycleObserver;
|
import androidx.lifecycle.DefaultLifecycleObserver;
|
||||||
import androidx.lifecycle.LifecycleOwner;
|
import androidx.lifecycle.LifecycleOwner;
|
||||||
import androidx.preference.PreferenceScreen;
|
import androidx.preference.PreferenceScreen;
|
||||||
|
|
||||||
|
import com.android.internal.annotations.VisibleForTesting;
|
||||||
import com.android.settings.R;
|
import com.android.settings.R;
|
||||||
import com.android.settings.bluetooth.Utils;
|
import com.android.settings.bluetooth.Utils;
|
||||||
import com.android.settings.connecteddevice.ConnectedDeviceDashboardFragment;
|
import com.android.settings.connecteddevice.ConnectedDeviceDashboardFragment;
|
||||||
@@ -55,13 +58,35 @@ public class AudioStreamsProgressCategoryController extends BasePreferenceContro
|
|||||||
implements DefaultLifecycleObserver {
|
implements DefaultLifecycleObserver {
|
||||||
private static final String TAG = "AudioStreamsProgressCategoryController";
|
private static final String TAG = "AudioStreamsProgressCategoryController";
|
||||||
private static final boolean DEBUG = BluetoothUtils.D;
|
private static final boolean DEBUG = BluetoothUtils.D;
|
||||||
private static final int UNSET_BROADCAST_ID = -1;
|
@VisibleForTesting static final int UNSET_BROADCAST_ID = -1;
|
||||||
private final BluetoothCallback mBluetoothCallback =
|
|
||||||
|
@VisibleForTesting
|
||||||
|
final BluetoothCallback mBluetoothCallback =
|
||||||
new BluetoothCallback() {
|
new BluetoothCallback() {
|
||||||
@Override
|
@Override
|
||||||
public void onActiveDeviceChanged(
|
public void onBluetoothStateChanged(@AdapterState int bluetoothState) {
|
||||||
@Nullable CachedBluetoothDevice activeDevice, int bluetoothProfile) {
|
Log.d(TAG, "onBluetoothStateChanged() with bluetoothState : " + bluetoothState);
|
||||||
if (bluetoothProfile == BluetoothProfile.LE_AUDIO) {
|
if (bluetoothState == BluetoothAdapter.STATE_OFF) {
|
||||||
|
mExecutor.execute(() -> init());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onProfileConnectionStateChanged(
|
||||||
|
@NonNull CachedBluetoothDevice cachedDevice,
|
||||||
|
@ConnectionState int state,
|
||||||
|
int bluetoothProfile) {
|
||||||
|
Log.d(
|
||||||
|
TAG,
|
||||||
|
"onProfileConnectionStateChanged() with cachedDevice : "
|
||||||
|
+ cachedDevice.getAddress()
|
||||||
|
+ " with state : "
|
||||||
|
+ state
|
||||||
|
+ " on profile : "
|
||||||
|
+ bluetoothProfile);
|
||||||
|
if (bluetoothProfile == BluetoothProfile.LE_AUDIO_BROADCAST_ASSISTANT
|
||||||
|
&& (state == BluetoothAdapter.STATE_CONNECTED
|
||||||
|
|| state == BluetoothAdapter.STATE_DISCONNECTED)) {
|
||||||
mExecutor.execute(() -> init());
|
mExecutor.execute(() -> init());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -92,7 +117,7 @@ public class AudioStreamsProgressCategoryController extends BasePreferenceContro
|
|||||||
SOURCE_ADDED,
|
SOURCE_ADDED,
|
||||||
}
|
}
|
||||||
|
|
||||||
private final Executor mExecutor;
|
@VisibleForTesting Executor mExecutor;
|
||||||
private final AudioStreamsProgressCategoryCallback mBroadcastAssistantCallback;
|
private final AudioStreamsProgressCategoryCallback mBroadcastAssistantCallback;
|
||||||
private final AudioStreamsHelper mAudioStreamsHelper;
|
private final AudioStreamsHelper mAudioStreamsHelper;
|
||||||
private final MediaControlHelper mMediaControlHelper;
|
private final MediaControlHelper mMediaControlHelper;
|
||||||
@@ -103,7 +128,7 @@ public class AudioStreamsProgressCategoryController extends BasePreferenceContro
|
|||||||
private @Nullable BluetoothLeBroadcastMetadata mSourceFromQrCode;
|
private @Nullable BluetoothLeBroadcastMetadata mSourceFromQrCode;
|
||||||
private SourceOriginForLogging mSourceFromQrCodeOriginForLogging;
|
private SourceOriginForLogging mSourceFromQrCodeOriginForLogging;
|
||||||
@Nullable private AudioStreamsProgressCategoryPreference mCategoryPreference;
|
@Nullable private AudioStreamsProgressCategoryPreference mCategoryPreference;
|
||||||
@Nullable private AudioStreamsDashboardFragment mFragment;
|
@Nullable private Fragment mFragment;
|
||||||
|
|
||||||
public AudioStreamsProgressCategoryController(Context context, String preferenceKey) {
|
public AudioStreamsProgressCategoryController(Context context, String preferenceKey) {
|
||||||
super(context, preferenceKey);
|
super(context, preferenceKey);
|
||||||
@@ -142,12 +167,12 @@ public class AudioStreamsProgressCategoryController extends BasePreferenceContro
|
|||||||
mExecutor.execute(this::stopScanning);
|
mExecutor.execute(this::stopScanning);
|
||||||
}
|
}
|
||||||
|
|
||||||
void setFragment(AudioStreamsDashboardFragment fragment) {
|
void setFragment(Fragment fragment) {
|
||||||
mFragment = fragment;
|
mFragment = fragment;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
AudioStreamsDashboardFragment getFragment() {
|
Fragment getFragment() {
|
||||||
return mFragment;
|
return mFragment;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -546,7 +571,8 @@ public class AudioStreamsProgressCategoryController extends BasePreferenceContro
|
|||||||
return preference;
|
return preference;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void moveToState(AudioStreamPreference preference, AudioStreamState state) {
|
@VisibleForTesting
|
||||||
|
void moveToState(AudioStreamPreference preference, AudioStreamState state) {
|
||||||
AudioStreamStateHandler stateHandler =
|
AudioStreamStateHandler stateHandler =
|
||||||
switch (state) {
|
switch (state) {
|
||||||
case SYNCED -> SyncedState.getInstance();
|
case SYNCED -> SyncedState.getInstance();
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ import androidx.preference.Preference;
|
|||||||
|
|
||||||
import com.android.settings.R;
|
import com.android.settings.R;
|
||||||
import com.android.settings.core.SubSettingLauncher;
|
import com.android.settings.core.SubSettingLauncher;
|
||||||
|
import com.android.settings.dashboard.DashboardFragment;
|
||||||
|
|
||||||
class SourceAddedState extends AudioStreamStateHandler {
|
class SourceAddedState extends AudioStreamStateHandler {
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
@@ -84,9 +85,10 @@ class SourceAddedState extends AudioStreamStateHandler {
|
|||||||
.setTitleRes(R.string.audio_streams_detail_page_title)
|
.setTitleRes(R.string.audio_streams_detail_page_title)
|
||||||
.setDestination(AudioStreamDetailsFragment.class.getName())
|
.setDestination(AudioStreamDetailsFragment.class.getName())
|
||||||
.setSourceMetricsCategory(
|
.setSourceMetricsCategory(
|
||||||
controller.getFragment() == null
|
!(controller.getFragment() instanceof DashboardFragment)
|
||||||
? SettingsEnums.PAGE_UNKNOWN
|
? SettingsEnums.PAGE_UNKNOWN
|
||||||
: controller.getFragment().getMetricsCategory())
|
: ((DashboardFragment) controller.getFragment())
|
||||||
|
.getMetricsCategory())
|
||||||
.setArguments(broadcast)
|
.setArguments(broadcast)
|
||||||
.launch();
|
.launch();
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@@ -0,0 +1,671 @@
|
|||||||
|
/*
|
||||||
|
* 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 com.android.settings.connecteddevice.audiosharing.audiostreams.AudioStreamsProgressCategoryController.AudioStreamState.ADD_SOURCE_BAD_CODE;
|
||||||
|
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.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.google.common.truth.Truth.assertThat;
|
||||||
|
|
||||||
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
|
import static org.mockito.ArgumentMatchers.anyBoolean;
|
||||||
|
import static org.mockito.ArgumentMatchers.anyString;
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
import static org.mockito.Mockito.never;
|
||||||
|
import static org.mockito.Mockito.spy;
|
||||||
|
import static org.mockito.Mockito.times;
|
||||||
|
import static org.mockito.Mockito.verify;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
import static org.robolectric.Shadows.shadowOf;
|
||||||
|
|
||||||
|
import static java.util.Collections.emptyList;
|
||||||
|
|
||||||
|
import android.bluetooth.BluetoothAdapter;
|
||||||
|
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.view.View;
|
||||||
|
import android.widget.Button;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import androidx.fragment.app.Fragment;
|
||||||
|
import androidx.lifecycle.LifecycleOwner;
|
||||||
|
import androidx.preference.PreferenceScreen;
|
||||||
|
import androidx.test.core.app.ApplicationProvider;
|
||||||
|
|
||||||
|
import com.android.settings.R;
|
||||||
|
import com.android.settings.connecteddevice.audiosharing.audiostreams.testshadows.ShadowAudioStreamsHelper;
|
||||||
|
import com.android.settings.testutils.shadow.ShadowBluetoothUtils;
|
||||||
|
import com.android.settings.testutils.shadow.ShadowThreadUtils;
|
||||||
|
import com.android.settingslib.bluetooth.BluetoothEventManager;
|
||||||
|
import com.android.settingslib.bluetooth.BluetoothLeBroadcastMetadataExt;
|
||||||
|
import com.android.settingslib.bluetooth.CachedBluetoothDevice;
|
||||||
|
import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant;
|
||||||
|
import com.android.settingslib.bluetooth.LocalBluetoothManager;
|
||||||
|
import com.android.settingslib.core.lifecycle.Lifecycle;
|
||||||
|
|
||||||
|
import com.google.common.collect.ImmutableList;
|
||||||
|
|
||||||
|
import org.junit.After;
|
||||||
|
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;
|
||||||
|
import org.robolectric.shadows.ShadowAlertDialog;
|
||||||
|
import org.robolectric.shadows.androidx.fragment.FragmentController;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@RunWith(RobolectricTestRunner.class)
|
||||||
|
@Config(
|
||||||
|
shadows = {
|
||||||
|
ShadowBluetoothUtils.class,
|
||||||
|
ShadowAudioStreamsHelper.class,
|
||||||
|
ShadowThreadUtils.class,
|
||||||
|
ShadowAlertDialog.class,
|
||||||
|
})
|
||||||
|
public class AudioStreamsProgressCategoryControllerTest {
|
||||||
|
@Rule public final MockitoRule mMockitoRule = MockitoJUnit.rule();
|
||||||
|
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=;;";
|
||||||
|
private static final String KEY = "audio_streams_nearby_category";
|
||||||
|
private static final int QR_CODE_BROADCAST_ID = 1;
|
||||||
|
private static final int ALREADY_CONNECTED_BROADCAST_ID = 2;
|
||||||
|
private static final int NEWLY_FOUND_BROADCAST_ID = 3;
|
||||||
|
private static final String BROADCAST_NAME_1 = "name_1";
|
||||||
|
private static final String BROADCAST_NAME_2 = "name_2";
|
||||||
|
private static final byte[] BROADCAST_CODE = new byte[] {1};
|
||||||
|
private final Context mContext = ApplicationProvider.getApplicationContext();
|
||||||
|
@Mock private LocalBluetoothManager mLocalBtManager;
|
||||||
|
@Mock private BluetoothEventManager mBluetoothEventManager;
|
||||||
|
@Mock private PreferenceScreen mScreen;
|
||||||
|
@Mock private AudioStreamsHelper mAudioStreamsHelper;
|
||||||
|
@Mock private LocalBluetoothLeBroadcastAssistant mLeBroadcastAssistant;
|
||||||
|
@Mock private BluetoothLeBroadcastMetadata mMetadata;
|
||||||
|
@Mock private CachedBluetoothDevice mDevice;
|
||||||
|
@Mock private AudioStreamsProgressCategoryPreference mPreference;
|
||||||
|
private Lifecycle mLifecycle;
|
||||||
|
private LifecycleOwner mLifecycleOwner;
|
||||||
|
private Fragment mFragment;
|
||||||
|
private TestController mController;
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setUp() {
|
||||||
|
ShadowAudioStreamsHelper.setUseMock(mAudioStreamsHelper);
|
||||||
|
when(mAudioStreamsHelper.getLeBroadcastAssistant()).thenReturn(mLeBroadcastAssistant);
|
||||||
|
when(mAudioStreamsHelper.getAllConnectedSources()).thenReturn(emptyList());
|
||||||
|
|
||||||
|
ShadowBluetoothUtils.sLocalBluetoothManager = mLocalBtManager;
|
||||||
|
when(mLocalBtManager.getEventManager()).thenReturn(mBluetoothEventManager);
|
||||||
|
when(mLeBroadcastAssistant.isSearchInProgress()).thenReturn(false);
|
||||||
|
|
||||||
|
when(mScreen.findPreference(anyString())).thenReturn(mPreference);
|
||||||
|
|
||||||
|
mLifecycleOwner = () -> mLifecycle;
|
||||||
|
mLifecycle = new Lifecycle(mLifecycleOwner);
|
||||||
|
|
||||||
|
mFragment = new Fragment();
|
||||||
|
mController = spy(new TestController(mContext, KEY));
|
||||||
|
}
|
||||||
|
|
||||||
|
@After
|
||||||
|
public void tearDown() {
|
||||||
|
ShadowBluetoothUtils.reset();
|
||||||
|
ShadowAudioStreamsHelper.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testGetAvailabilityStatus() {
|
||||||
|
int status = mController.getAvailabilityStatus();
|
||||||
|
|
||||||
|
assertThat(status).isEqualTo(AVAILABLE);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testDisplayPreference() {
|
||||||
|
mController.displayPreference(mScreen);
|
||||||
|
|
||||||
|
verify(mPreference).setVisible(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSetScanning() {
|
||||||
|
mController.displayPreference(mScreen);
|
||||||
|
mController.setScanning(true);
|
||||||
|
|
||||||
|
verify(mPreference).setProgress(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testOnStart_initNoDevice_showDialog() {
|
||||||
|
when(mLeBroadcastAssistant.isSearchInProgress()).thenReturn(true);
|
||||||
|
|
||||||
|
FragmentController.setupFragment(mFragment);
|
||||||
|
mController.setFragment(mFragment);
|
||||||
|
mController.displayPreference(mScreen);
|
||||||
|
mController.onStart(mLifecycleOwner);
|
||||||
|
shadowOf(Looper.getMainLooper()).idle();
|
||||||
|
|
||||||
|
// Called twice, once in displayPreference, the other in init()
|
||||||
|
verify(mPreference, times(2)).setVisible(anyBoolean());
|
||||||
|
verify(mPreference).removeAudioStreamPreferences();
|
||||||
|
verify(mLeBroadcastAssistant).stopSearchingForSources();
|
||||||
|
verify(mLeBroadcastAssistant).unregisterServiceCallBack(any());
|
||||||
|
|
||||||
|
var dialog = ShadowAlertDialog.getLatestAlertDialog();
|
||||||
|
assertThat(dialog).isNotNull();
|
||||||
|
assertThat(dialog.isShowing()).isTrue();
|
||||||
|
|
||||||
|
TextView title = dialog.findViewById(R.id.dialog_title);
|
||||||
|
assertThat(title).isNotNull();
|
||||||
|
assertThat(title.getText())
|
||||||
|
.isEqualTo(mContext.getString(R.string.audio_streams_dialog_no_le_device_title));
|
||||||
|
TextView subtitle1 = dialog.findViewById(R.id.dialog_subtitle);
|
||||||
|
assertThat(subtitle1).isNotNull();
|
||||||
|
assertThat(subtitle1.getVisibility()).isEqualTo(View.GONE);
|
||||||
|
TextView subtitle2 = dialog.findViewById(R.id.dialog_subtitle_2);
|
||||||
|
assertThat(subtitle2).isNotNull();
|
||||||
|
assertThat(subtitle2.getText())
|
||||||
|
.isEqualTo(mContext.getString(R.string.audio_streams_dialog_no_le_device_subtitle));
|
||||||
|
View leftButton = dialog.findViewById(R.id.left_button);
|
||||||
|
assertThat(leftButton).isNotNull();
|
||||||
|
assertThat(leftButton.getVisibility()).isEqualTo(View.VISIBLE);
|
||||||
|
Button rightButton = dialog.findViewById(R.id.right_button);
|
||||||
|
assertThat(rightButton).isNotNull();
|
||||||
|
assertThat(rightButton.getText())
|
||||||
|
.isEqualTo(mContext.getString(R.string.audio_streams_dialog_no_le_device_button));
|
||||||
|
assertThat(rightButton.hasOnClickListeners()).isTrue();
|
||||||
|
|
||||||
|
dialog.cancel();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testBluetoothOff_triggerRunnable() {
|
||||||
|
mController.mBluetoothCallback.onBluetoothStateChanged(BluetoothAdapter.STATE_OFF);
|
||||||
|
|
||||||
|
verify(mController.mExecutor).execute(any());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testDeviceConnectionStateChanged_triggerRunnable() {
|
||||||
|
mController.mBluetoothCallback.onProfileConnectionStateChanged(
|
||||||
|
mDevice,
|
||||||
|
BluetoothAdapter.STATE_DISCONNECTED,
|
||||||
|
BluetoothProfile.LE_AUDIO_BROADCAST_ASSISTANT);
|
||||||
|
|
||||||
|
verify(mController.mExecutor).execute(any());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testOnStart_initHasDevice_noPreference() {
|
||||||
|
// Setup a device
|
||||||
|
ShadowAudioStreamsHelper.setCachedBluetoothDeviceInSharingOrLeConnected(mDevice);
|
||||||
|
|
||||||
|
mController.onStart(mLifecycleOwner);
|
||||||
|
shadowOf(Looper.getMainLooper()).idle();
|
||||||
|
|
||||||
|
verify(mLeBroadcastAssistant).registerServiceCallBack(any(), any());
|
||||||
|
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
|
||||||
|
ShadowAudioStreamsHelper.setCachedBluetoothDeviceInSharingOrLeConnected(mDevice);
|
||||||
|
|
||||||
|
// Setup a source from qr code
|
||||||
|
mController.setSourceFromQrCode(mMetadata, SourceOriginForLogging.UNKNOWN);
|
||||||
|
when(mMetadata.getBroadcastId()).thenReturn(QR_CODE_BROADCAST_ID);
|
||||||
|
|
||||||
|
// Handle the source from qr code in onStart
|
||||||
|
mController.displayPreference(mScreen);
|
||||||
|
mController.onStart(mLifecycleOwner);
|
||||||
|
shadowOf(Looper.getMainLooper()).idle();
|
||||||
|
|
||||||
|
// Verify the connected source is created and moved to WAIT_FOR_SYNC
|
||||||
|
ArgumentCaptor<AudioStreamPreference> preference =
|
||||||
|
ArgumentCaptor.forClass(AudioStreamPreference.class);
|
||||||
|
ArgumentCaptor<AudioStreamsProgressCategoryController.AudioStreamState> state =
|
||||||
|
ArgumentCaptor.forClass(
|
||||||
|
AudioStreamsProgressCategoryController.AudioStreamState.class);
|
||||||
|
|
||||||
|
verify(mController).moveToState(preference.capture(), state.capture());
|
||||||
|
assertThat(preference.getValue()).isNotNull();
|
||||||
|
assertThat(preference.getValue().getAudioStreamBroadcastId())
|
||||||
|
.isEqualTo(QR_CODE_BROADCAST_ID);
|
||||||
|
assertThat(state.getValue()).isEqualTo(WAIT_FOR_SYNC);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testOnStart_handleSourceAlreadyConnected() {
|
||||||
|
// Setup a device
|
||||||
|
ShadowAudioStreamsHelper.setCachedBluetoothDeviceInSharingOrLeConnected(mDevice);
|
||||||
|
|
||||||
|
// Setup a connected source
|
||||||
|
BluetoothLeBroadcastReceiveState connected =
|
||||||
|
createConnectedMock(ALREADY_CONNECTED_BROADCAST_ID);
|
||||||
|
List<BluetoothLeBroadcastReceiveState> list = new ArrayList<>();
|
||||||
|
list.add(connected);
|
||||||
|
when(mAudioStreamsHelper.getAllConnectedSources()).thenReturn(list);
|
||||||
|
|
||||||
|
// Handle already connected source in onStart
|
||||||
|
mController.displayPreference(mScreen);
|
||||||
|
mController.onStart(mLifecycleOwner);
|
||||||
|
shadowOf(Looper.getMainLooper()).idle();
|
||||||
|
|
||||||
|
ArgumentCaptor<AudioStreamPreference> preference =
|
||||||
|
ArgumentCaptor.forClass(AudioStreamPreference.class);
|
||||||
|
ArgumentCaptor<AudioStreamsProgressCategoryController.AudioStreamState> state =
|
||||||
|
ArgumentCaptor.forClass(
|
||||||
|
AudioStreamsProgressCategoryController.AudioStreamState.class);
|
||||||
|
|
||||||
|
// Verify the connected source is created and moved to SOURCE_ADDED
|
||||||
|
verify(mController).moveToState(preference.capture(), state.capture());
|
||||||
|
assertThat(preference.getValue()).isNotNull();
|
||||||
|
assertThat(preference.getValue().getAudioStreamBroadcastId())
|
||||||
|
.isEqualTo(ALREADY_CONNECTED_BROADCAST_ID);
|
||||||
|
assertThat(state.getValue()).isEqualTo(SOURCE_ADDED);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testOnStart_sourceFromQrCodeNoId_sourceAlreadyConnected_sameName_updateId() {
|
||||||
|
// Setup a device
|
||||||
|
ShadowAudioStreamsHelper.setCachedBluetoothDeviceInSharingOrLeConnected(mDevice);
|
||||||
|
|
||||||
|
// Setup source from qr code with unset id and BROADCAST_NAME_1. Creating a real metadata
|
||||||
|
// for properly update its id.
|
||||||
|
var metadata =
|
||||||
|
BluetoothLeBroadcastMetadataExt.INSTANCE.convertToBroadcastMetadata(VALID_METADATA);
|
||||||
|
assertThat(metadata).isNotNull();
|
||||||
|
var metadataWithNoIdAndSameName =
|
||||||
|
new BluetoothLeBroadcastMetadata.Builder(metadata)
|
||||||
|
.setBroadcastId(UNSET_BROADCAST_ID)
|
||||||
|
.setBroadcastName(BROADCAST_NAME_1)
|
||||||
|
.build();
|
||||||
|
mController.setSourceFromQrCode(
|
||||||
|
metadataWithNoIdAndSameName, SourceOriginForLogging.UNKNOWN);
|
||||||
|
|
||||||
|
// Setup a connected source with name BROADCAST_NAME_1 and id
|
||||||
|
BluetoothLeBroadcastReceiveState connected =
|
||||||
|
createConnectedMock(ALREADY_CONNECTED_BROADCAST_ID);
|
||||||
|
var data = mock(BluetoothLeAudioContentMetadata.class);
|
||||||
|
when(connected.getSubgroupMetadata()).thenReturn(ImmutableList.of(data));
|
||||||
|
when(data.getProgramInfo()).thenReturn(BROADCAST_NAME_1);
|
||||||
|
when(mAudioStreamsHelper.getAllConnectedSources()).thenReturn(ImmutableList.of(connected));
|
||||||
|
|
||||||
|
// Handle both source from qr code and already connected source in onStart
|
||||||
|
mController.displayPreference(mScreen);
|
||||||
|
mController.onStart(mLifecycleOwner);
|
||||||
|
shadowOf(Looper.getMainLooper()).idle();
|
||||||
|
|
||||||
|
// Verify two preferences created, one moved to state WAIT_FOR_SYNC, one to SOURCE_ADDED.
|
||||||
|
// Both has ALREADY_CONNECTED_BROADCAST_ID as the UNSET_ID is updated to match.
|
||||||
|
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);
|
||||||
|
|
||||||
|
// The preference contains source from qr code
|
||||||
|
assertThat(preferences.get(0).getAudioStreamBroadcastId())
|
||||||
|
.isEqualTo(ALREADY_CONNECTED_BROADCAST_ID);
|
||||||
|
assertThat(states.get(0)).isEqualTo(WAIT_FOR_SYNC);
|
||||||
|
|
||||||
|
// The preference contains already connected source
|
||||||
|
assertThat(preferences.get(1).getAudioStreamBroadcastId())
|
||||||
|
.isEqualTo(ALREADY_CONNECTED_BROADCAST_ID);
|
||||||
|
assertThat(states.get(1)).isEqualTo(SOURCE_ADDED);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testHandleSourceFound_addNew() {
|
||||||
|
// Setup a device
|
||||||
|
ShadowAudioStreamsHelper.setCachedBluetoothDeviceInSharingOrLeConnected(mDevice);
|
||||||
|
|
||||||
|
when(mMetadata.getBroadcastId()).thenReturn(NEWLY_FOUND_BROADCAST_ID);
|
||||||
|
// A new source is found
|
||||||
|
mController.handleSourceFound(mMetadata);
|
||||||
|
|
||||||
|
// Verify a preference is created with state SYNCED.
|
||||||
|
ArgumentCaptor<AudioStreamPreference> preference =
|
||||||
|
ArgumentCaptor.forClass(AudioStreamPreference.class);
|
||||||
|
ArgumentCaptor<AudioStreamsProgressCategoryController.AudioStreamState> state =
|
||||||
|
ArgumentCaptor.forClass(
|
||||||
|
AudioStreamsProgressCategoryController.AudioStreamState.class);
|
||||||
|
|
||||||
|
verify(mController).moveToState(preference.capture(), state.capture());
|
||||||
|
assertThat(preference.getValue()).isNotNull();
|
||||||
|
assertThat(preference.getValue().getAudioStreamBroadcastId())
|
||||||
|
.isEqualTo(NEWLY_FOUND_BROADCAST_ID);
|
||||||
|
assertThat(state.getValue()).isEqualTo(SYNCED);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testHandleSourceFound_sameIdWithSourceFromQrCode_updateMetadataAndState() {
|
||||||
|
// Setup a device
|
||||||
|
ShadowAudioStreamsHelper.setCachedBluetoothDeviceInSharingOrLeConnected(mDevice);
|
||||||
|
|
||||||
|
// Setup source from qr code with QR_CODE_BROADCAST_ID, BROADCAST_NAME_1 and BROADCAST_CODE.
|
||||||
|
var metadata =
|
||||||
|
BluetoothLeBroadcastMetadataExt.INSTANCE.convertToBroadcastMetadata(VALID_METADATA);
|
||||||
|
assertThat(metadata).isNotNull();
|
||||||
|
var metadataFromQrCode =
|
||||||
|
new BluetoothLeBroadcastMetadata.Builder(metadata)
|
||||||
|
.setBroadcastId(QR_CODE_BROADCAST_ID)
|
||||||
|
.setBroadcastName(BROADCAST_NAME_1)
|
||||||
|
.setBroadcastCode(BROADCAST_CODE)
|
||||||
|
.build();
|
||||||
|
mController.setSourceFromQrCode(metadataFromQrCode, SourceOriginForLogging.UNKNOWN);
|
||||||
|
|
||||||
|
// Handle the source from qr code in onStart
|
||||||
|
mController.displayPreference(mScreen);
|
||||||
|
mController.onStart(mLifecycleOwner);
|
||||||
|
shadowOf(Looper.getMainLooper()).idle();
|
||||||
|
|
||||||
|
// A new source is found
|
||||||
|
mController.handleSourceFound(
|
||||||
|
new BluetoothLeBroadcastMetadata.Builder(metadata)
|
||||||
|
.setBroadcastId(QR_CODE_BROADCAST_ID)
|
||||||
|
.setBroadcastName(BROADCAST_NAME_2)
|
||||||
|
.build());
|
||||||
|
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();
|
||||||
|
List<AudioStreamsProgressCategoryController.AudioStreamState> states = state.getAllValues();
|
||||||
|
|
||||||
|
// Verify the qr code source is created with WAIT_FOR_SYNC, broadcast name got updated to
|
||||||
|
// BROADCAST_NAME_2
|
||||||
|
var sourceFromQrCode = preferences.get(0);
|
||||||
|
assertThat(sourceFromQrCode.getAudioStreamBroadcastId()).isEqualTo(QR_CODE_BROADCAST_ID);
|
||||||
|
assertThat(sourceFromQrCode.getAudioStreamMetadata()).isNotNull();
|
||||||
|
assertThat(sourceFromQrCode.getAudioStreamMetadata().getBroadcastName())
|
||||||
|
.isEqualTo(BROADCAST_NAME_2);
|
||||||
|
assertThat(sourceFromQrCode.getAudioStreamMetadata().getBroadcastCode())
|
||||||
|
.isEqualTo(BROADCAST_CODE);
|
||||||
|
assertThat(states.get(0)).isEqualTo(WAIT_FOR_SYNC);
|
||||||
|
|
||||||
|
// Verify the newly found source is created, broadcast code is retrieved from the source
|
||||||
|
// from qr code, and state updated to ADD_SOURCE_WAIT_FOR_RESPONSE
|
||||||
|
var newlyFoundSource = preferences.get(1);
|
||||||
|
assertThat(newlyFoundSource.getAudioStreamBroadcastId()).isEqualTo(QR_CODE_BROADCAST_ID);
|
||||||
|
assertThat(newlyFoundSource.getAudioStreamMetadata()).isNotNull();
|
||||||
|
assertThat(newlyFoundSource.getAudioStreamMetadata().getBroadcastName())
|
||||||
|
.isEqualTo(BROADCAST_NAME_2);
|
||||||
|
assertThat(newlyFoundSource.getAudioStreamMetadata().getBroadcastCode())
|
||||||
|
.isEqualTo(BROADCAST_CODE);
|
||||||
|
assertThat(states.get(1)).isEqualTo(ADD_SOURCE_WAIT_FOR_RESPONSE);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testHandleSourceFound_sameIdWithOtherState_doNothing() {
|
||||||
|
// Setup a device
|
||||||
|
ShadowAudioStreamsHelper.setCachedBluetoothDeviceInSharingOrLeConnected(mDevice);
|
||||||
|
|
||||||
|
// Setup source already connected
|
||||||
|
BluetoothLeBroadcastReceiveState connected =
|
||||||
|
createConnectedMock(ALREADY_CONNECTED_BROADCAST_ID);
|
||||||
|
when(mAudioStreamsHelper.getAllConnectedSources()).thenReturn(ImmutableList.of(connected));
|
||||||
|
|
||||||
|
// Handle source already connected in onStart
|
||||||
|
mController.displayPreference(mScreen);
|
||||||
|
mController.onStart(mLifecycleOwner);
|
||||||
|
shadowOf(Looper.getMainLooper()).idle();
|
||||||
|
|
||||||
|
// A new source found
|
||||||
|
when(mMetadata.getBroadcastId()).thenReturn(ALREADY_CONNECTED_BROADCAST_ID);
|
||||||
|
mController.handleSourceFound(mMetadata);
|
||||||
|
shadowOf(Looper.getMainLooper()).idle();
|
||||||
|
|
||||||
|
// Verify only the connected source has created a preference, and its state remains as
|
||||||
|
// SOURCE_ADDED
|
||||||
|
ArgumentCaptor<AudioStreamPreference> preference =
|
||||||
|
ArgumentCaptor.forClass(AudioStreamPreference.class);
|
||||||
|
ArgumentCaptor<AudioStreamsProgressCategoryController.AudioStreamState> state =
|
||||||
|
ArgumentCaptor.forClass(
|
||||||
|
AudioStreamsProgressCategoryController.AudioStreamState.class);
|
||||||
|
|
||||||
|
verify(mController).moveToState(preference.capture(), state.capture());
|
||||||
|
assertThat(preference.getValue()).isNotNull();
|
||||||
|
assertThat(preference.getValue().getAudioStreamBroadcastId())
|
||||||
|
.isEqualTo(ALREADY_CONNECTED_BROADCAST_ID);
|
||||||
|
assertThat(preference.getValue().getAudioStreamState()).isEqualTo(SOURCE_ADDED);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testHandleSourceLost_removed() {
|
||||||
|
// 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();
|
||||||
|
|
||||||
|
// A new source found is lost
|
||||||
|
mController.handleSourceLost(NEWLY_FOUND_BROADCAST_ID);
|
||||||
|
shadowOf(Looper.getMainLooper()).idle();
|
||||||
|
|
||||||
|
ArgumentCaptor<AudioStreamPreference> preferenceToAdd =
|
||||||
|
ArgumentCaptor.forClass(AudioStreamPreference.class);
|
||||||
|
ArgumentCaptor<AudioStreamPreference> preferenceToRemove =
|
||||||
|
ArgumentCaptor.forClass(AudioStreamPreference.class);
|
||||||
|
ArgumentCaptor<AudioStreamsProgressCategoryController.AudioStreamState> state =
|
||||||
|
ArgumentCaptor.forClass(
|
||||||
|
AudioStreamsProgressCategoryController.AudioStreamState.class);
|
||||||
|
|
||||||
|
// Verify a new preference is created with state SYNCED.
|
||||||
|
verify(mController).moveToState(preferenceToAdd.capture(), state.capture());
|
||||||
|
assertThat(preferenceToAdd.getValue()).isNotNull();
|
||||||
|
assertThat(preferenceToAdd.getValue().getAudioStreamBroadcastId())
|
||||||
|
.isEqualTo(NEWLY_FOUND_BROADCAST_ID);
|
||||||
|
assertThat(state.getValue()).isEqualTo(SYNCED);
|
||||||
|
|
||||||
|
// Verify the preference with NEWLY_FOUND_BROADCAST_ID is removed.
|
||||||
|
verify(mPreference).removePreference(preferenceToRemove.capture());
|
||||||
|
assertThat(preferenceToRemove.getValue().getAudioStreamBroadcastId())
|
||||||
|
.isEqualTo(NEWLY_FOUND_BROADCAST_ID);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testHandleSourceRemoved_removed() {
|
||||||
|
// Setup a device
|
||||||
|
ShadowAudioStreamsHelper.setCachedBluetoothDeviceInSharingOrLeConnected(mDevice);
|
||||||
|
|
||||||
|
// Setup already connected source
|
||||||
|
BluetoothLeBroadcastReceiveState connected =
|
||||||
|
createConnectedMock(ALREADY_CONNECTED_BROADCAST_ID);
|
||||||
|
when(mAudioStreamsHelper.getAllConnectedSources()).thenReturn(ImmutableList.of(connected));
|
||||||
|
|
||||||
|
// Handle connected source in onStart
|
||||||
|
mController.displayPreference(mScreen);
|
||||||
|
mController.onStart(mLifecycleOwner);
|
||||||
|
shadowOf(Looper.getMainLooper()).idle();
|
||||||
|
|
||||||
|
// The connect source is no longer connected
|
||||||
|
when(mAudioStreamsHelper.getAllConnectedSources()).thenReturn(emptyList());
|
||||||
|
mController.handleSourceRemoved();
|
||||||
|
shadowOf(Looper.getMainLooper()).idle();
|
||||||
|
|
||||||
|
ArgumentCaptor<AudioStreamPreference> preferenceToAdd =
|
||||||
|
ArgumentCaptor.forClass(AudioStreamPreference.class);
|
||||||
|
ArgumentCaptor<AudioStreamPreference> preferenceToRemove =
|
||||||
|
ArgumentCaptor.forClass(AudioStreamPreference.class);
|
||||||
|
ArgumentCaptor<AudioStreamsProgressCategoryController.AudioStreamState> state =
|
||||||
|
ArgumentCaptor.forClass(
|
||||||
|
AudioStreamsProgressCategoryController.AudioStreamState.class);
|
||||||
|
|
||||||
|
// Verify a new preference is created with state SOURCE_ADDED.
|
||||||
|
verify(mController).moveToState(preferenceToAdd.capture(), state.capture());
|
||||||
|
assertThat(preferenceToAdd.getValue()).isNotNull();
|
||||||
|
assertThat(preferenceToAdd.getValue().getAudioStreamBroadcastId())
|
||||||
|
.isEqualTo(ALREADY_CONNECTED_BROADCAST_ID);
|
||||||
|
assertThat(state.getValue()).isEqualTo(SOURCE_ADDED);
|
||||||
|
|
||||||
|
// Verify the preference with ALREADY_CONNECTED_BROADCAST_ID is removed.
|
||||||
|
verify(mPreference).removePreference(preferenceToRemove.capture());
|
||||||
|
assertThat(preferenceToRemove.getValue().getAudioStreamBroadcastId())
|
||||||
|
.isEqualTo(ALREADY_CONNECTED_BROADCAST_ID);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testHandleSourceRemoved_updateState() {
|
||||||
|
// Setup a device
|
||||||
|
ShadowAudioStreamsHelper.setCachedBluetoothDeviceInSharingOrLeConnected(mDevice);
|
||||||
|
|
||||||
|
// Setup a connected source
|
||||||
|
BluetoothLeBroadcastReceiveState connected =
|
||||||
|
createConnectedMock(ALREADY_CONNECTED_BROADCAST_ID);
|
||||||
|
when(mAudioStreamsHelper.getAllConnectedSources()).thenReturn(ImmutableList.of(connected));
|
||||||
|
|
||||||
|
// Handle connected source in onStart
|
||||||
|
mController.displayPreference(mScreen);
|
||||||
|
mController.onStart(mLifecycleOwner);
|
||||||
|
shadowOf(Looper.getMainLooper()).idle();
|
||||||
|
|
||||||
|
// The connected source is identified as having a bad code
|
||||||
|
BluetoothLeBroadcastReceiveState badCode = mock(BluetoothLeBroadcastReceiveState.class);
|
||||||
|
when(badCode.getBroadcastId()).thenReturn(ALREADY_CONNECTED_BROADCAST_ID);
|
||||||
|
when(badCode.getPaSyncState())
|
||||||
|
.thenReturn(BluetoothLeBroadcastReceiveState.PA_SYNC_STATE_SYNCHRONIZED);
|
||||||
|
when(badCode.getBigEncryptionState())
|
||||||
|
.thenReturn(BluetoothLeBroadcastReceiveState.BIG_ENCRYPTION_STATE_BAD_CODE);
|
||||||
|
mController.handleSourceConnectBadCode(badCode);
|
||||||
|
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 the connected source is created state SOURCE_ADDED
|
||||||
|
assertThat(preferences.get(0).getAudioStreamBroadcastId())
|
||||||
|
.isEqualTo(ALREADY_CONNECTED_BROADCAST_ID);
|
||||||
|
assertThat(states.get(0)).isEqualTo(SOURCE_ADDED);
|
||||||
|
|
||||||
|
// Verify the connected source is updated to state ADD_SOURCE_BAD_CODE
|
||||||
|
assertThat(preferences.get(1).getAudioStreamBroadcastId())
|
||||||
|
.isEqualTo(ALREADY_CONNECTED_BROADCAST_ID);
|
||||||
|
assertThat(states.get(1)).isEqualTo(ADD_SOURCE_BAD_CODE);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testHandleSourceFailedToConnect_updateState() {
|
||||||
|
// 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 new found source is identified as failed to connect
|
||||||
|
mController.handleSourceFailedToConnect(NEWLY_FOUND_BROADCAST_ID);
|
||||||
|
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(ADD_SOURCE_FAILED);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static BluetoothLeBroadcastReceiveState createConnectedMock(int id) {
|
||||||
|
var connected = mock(BluetoothLeBroadcastReceiveState.class);
|
||||||
|
List<Long> bisSyncState = new ArrayList<>();
|
||||||
|
bisSyncState.add(1L);
|
||||||
|
when(connected.getBroadcastId()).thenReturn(id);
|
||||||
|
when(connected.getBisSyncState()).thenReturn(bisSyncState);
|
||||||
|
return connected;
|
||||||
|
}
|
||||||
|
|
||||||
|
static class TestController extends AudioStreamsProgressCategoryController {
|
||||||
|
TestController(Context context, String preferenceKey) {
|
||||||
|
super(context, preferenceKey);
|
||||||
|
mExecutor = spy(mContext.getMainExecutor());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
void moveToState(AudioStreamPreference preference, AudioStreamState state) {
|
||||||
|
preference.setAudioStreamState(state);
|
||||||
|
// Do nothing else to avoid side effect from AudioStreamStateHandler#performAction
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -32,7 +32,7 @@ import org.robolectric.annotation.Resetter;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
|
||||||
@Implements(value = AudioStreamsHelper.class, callThroughByDefault = false)
|
@Implements(value = AudioStreamsHelper.class, callThroughByDefault = true)
|
||||||
public class ShadowAudioStreamsHelper {
|
public class ShadowAudioStreamsHelper {
|
||||||
private static AudioStreamsHelper sMockHelper;
|
private static AudioStreamsHelper sMockHelper;
|
||||||
@Nullable private static CachedBluetoothDevice sCachedBluetoothDevice;
|
@Nullable private static CachedBluetoothDevice sCachedBluetoothDevice;
|
||||||
|
|||||||
Reference in New Issue
Block a user