[Audiosharing] Show retry dialog when error occurs in start sharing

Errors including:
1. onBroadcastStartFailed
2. onSourceAddFailed

Test: atest
Flag: com.android.settingslib.flags.enable_le_audio_sharing
Bug: 362858894
Change-Id: I0fe13bd675672c57219d0f6f3e735515a7ed7a06
This commit is contained in:
Yiyi Shen
2024-09-20 17:11:47 +08:00
parent 8be12edf9c
commit 302b26ff74
2 changed files with 230 additions and 42 deletions

View File

@@ -41,7 +41,9 @@ import android.widget.CompoundButton.OnCheckedChangeListener;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.annotation.UiThread;
import androidx.annotation.VisibleForTesting; import androidx.annotation.VisibleForTesting;
import androidx.fragment.app.DialogFragment;
import androidx.fragment.app.Fragment; import androidx.fragment.app.Fragment;
import androidx.lifecycle.DefaultLifecycleObserver; import androidx.lifecycle.DefaultLifecycleObserver;
import androidx.lifecycle.LifecycleOwner; import androidx.lifecycle.LifecycleOwner;
@@ -69,7 +71,6 @@ import com.google.common.collect.ImmutableList;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Locale;
import java.util.Map; import java.util.Map;
import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
@@ -113,14 +114,21 @@ public class AudioSharingSwitchBarController extends BasePreferenceController
private final Executor mExecutor; private final Executor mExecutor;
private final MetricsFeatureProvider mMetricsFeatureProvider; private final MetricsFeatureProvider mMetricsFeatureProvider;
private final OnAudioSharingStateChangedListener mListener; private final OnAudioSharingStateChangedListener mListener;
@VisibleForTesting IntentFilter mIntentFilter;
private Map<Integer, List<BluetoothDevice>> mGroupedConnectedDevices = new HashMap<>(); private Map<Integer, List<BluetoothDevice>> mGroupedConnectedDevices = new HashMap<>();
@Nullable private AudioSharingDeviceItem mTargetActiveItem; @Nullable private AudioSharingDeviceItem mTargetActiveItem;
private List<AudioSharingDeviceItem> mDeviceItemsForSharing = new ArrayList<>(); private List<AudioSharingDeviceItem> mDeviceItemsForSharing = new ArrayList<>();
@VisibleForTesting IntentFilter mIntentFilter;
private final AtomicBoolean mCallbacksRegistered = new AtomicBoolean(false); private final AtomicBoolean mCallbacksRegistered = new AtomicBoolean(false);
private AtomicInteger mIntentHandleStage = private AtomicInteger mIntentHandleStage =
new AtomicInteger(StartIntentHandleStage.TO_HANDLE.ordinal()); new AtomicInteger(StartIntentHandleStage.TO_HANDLE.ordinal());
// The sinks in adding source process. We show the progress dialog based on this list.
private CopyOnWriteArrayList<BluetoothDevice> mSinksInAdding = new CopyOnWriteArrayList<>(); private CopyOnWriteArrayList<BluetoothDevice> mSinksInAdding = new CopyOnWriteArrayList<>();
// The primary/active sinks in adding source process.
// To avoid users advance to share then pair flow before the primary/active sinks successfully
// join the audio sharing, we will wait for the process complete for this list of sinks and then
// popup audio sharing dialog with options to pair new device.
private CopyOnWriteArrayList<BluetoothDevice> mSinksToWaitFor = new CopyOnWriteArrayList<>();
private AtomicBoolean mStoppingSharing = new AtomicBoolean(false);
@VisibleForTesting @VisibleForTesting
BroadcastReceiver mReceiver = BroadcastReceiver mReceiver =
@@ -153,6 +161,7 @@ public class AudioSharingSwitchBarController extends BasePreferenceController
public void onBroadcastStartFailed(int reason) { public void onBroadcastStartFailed(int reason) {
Log.d(TAG, "onBroadcastStartFailed(), reason = " + reason); Log.d(TAG, "onBroadcastStartFailed(), reason = " + reason);
updateSwitch(); updateSwitch();
showRetryDialog();
mMetricsFeatureProvider.action( mMetricsFeatureProvider.action(
mContext, mContext,
SettingsEnums.ACTION_AUDIO_SHARING_START_FAILED, SettingsEnums.ACTION_AUDIO_SHARING_START_FAILED,
@@ -178,7 +187,10 @@ public class AudioSharingSwitchBarController extends BasePreferenceController
+ reason + reason
+ ", broadcastId = " + ", broadcastId = "
+ broadcastId); + broadcastId);
mStoppingSharing.compareAndSet(true, false);
updateSwitch(); updateSwitch();
AudioSharingUtils.postOnMainThread(mContext,
() -> dismissStaleDialogsOtherThanRetryDialog());
AudioSharingUtils.toastMessage( AudioSharingUtils.toastMessage(
mContext, mContext,
mContext.getString(R.string.audio_sharing_sharing_stopped_label)); mContext.getString(R.string.audio_sharing_sharing_stopped_label));
@@ -219,7 +231,7 @@ public class AudioSharingSwitchBarController extends BasePreferenceController
TAG, TAG,
"Skip handleOnBroadcastReady: null assistant or " "Skip handleOnBroadcastReady: null assistant or "
+ "sink has active local source."); + "sink has active local source.");
cleanUp(); cleanUpStatesForStartSharing();
return; return;
} }
handleOnBroadcastReady(); handleOnBroadcastReady();
@@ -264,17 +276,14 @@ public class AudioSharingSwitchBarController extends BasePreferenceController
+ source + source
+ ", reason = " + ", reason = "
+ reason); + reason);
if (mSinksInAdding.contains(sink)) {
stopAudioSharing();
showRetryDialog();
mMetricsFeatureProvider.action( mMetricsFeatureProvider.action(
mContext, mContext,
SettingsEnums.ACTION_AUDIO_SHARING_JOIN_FAILED, SettingsEnums.ACTION_AUDIO_SHARING_JOIN_FAILED,
SettingsEnums.AUDIO_SHARING_SETTINGS); SettingsEnums.AUDIO_SHARING_SETTINGS);
AudioSharingUtils.toastMessage( }
mContext,
String.format(
Locale.US,
"Fail to add source to %s reason %d",
sink.getAddress(),
reason));
} }
@Override @Override
@@ -298,6 +307,10 @@ public class AudioSharingSwitchBarController extends BasePreferenceController
@NonNull BluetoothDevice sink, @NonNull BluetoothDevice sink,
int sourceId, int sourceId,
@NonNull BluetoothLeBroadcastReceiveState state) { @NonNull BluetoothLeBroadcastReceiveState state) {
if (mStoppingSharing.get()) {
Log.d(TAG, "Skip onReceiveStateChanged, stopping broadcast");
return;
}
if (BluetoothUtils.isConnected(state)) { if (BluetoothUtils.isConnected(state)) {
if (mSinksInAdding.contains(sink)) { if (mSinksInAdding.contains(sink)) {
mSinksInAdding.remove(sink); mSinksInAdding.remove(sink);
@@ -305,6 +318,22 @@ public class AudioSharingSwitchBarController extends BasePreferenceController
dismissLoadingStateDialogIfNeeded(); dismissLoadingStateDialogIfNeeded();
Log.d(TAG, "onReceiveStateChanged() connected, sink = " + sink Log.d(TAG, "onReceiveStateChanged() connected, sink = " + sink
+ ", remaining sinks = " + mSinksInAdding); + ", remaining sinks = " + mSinksInAdding);
if (mSinksToWaitFor.contains(sink)) {
mSinksToWaitFor.remove(sink);
if (mSinksToWaitFor.isEmpty()) {
// To avoid users advance to share then pair flow before the
// primary/active sinks successfully join the audio sharing,
// popup dialog till adding source complete for mSinksToWaitFor.
Pair<Integer, Object>[] eventData =
AudioSharingUtils.buildAudioSharingDialogEventData(
SettingsEnums.AUDIO_SHARING_SETTINGS,
SettingsEnums.DIALOG_AUDIO_SHARING_ADD_DEVICE,
/* userTriggered= */ false,
/* deviceCountInSharing= */ 1,
/* candidateDeviceCount= */ 0);
showAudioSharingDialog(eventData);
}
}
} }
} }
}; };
@@ -411,6 +440,8 @@ public class AudioSharingSwitchBarController extends BasePreferenceController
return; return;
} }
stopAudioSharing(); stopAudioSharing();
mMetricsFeatureProvider.action(
mContext, SettingsEnums.ACTION_AUDIO_SHARING_MAIN_SWITCH_OFF);
} }
} }
@@ -553,9 +584,14 @@ public class AudioSharingSwitchBarController extends BasePreferenceController
private void stopAudioSharing() { private void stopAudioSharing() {
if (mBroadcast != null) { if (mBroadcast != null) {
mBroadcast.stopBroadcast(mBroadcast.getLatestBroadcastId()); int broadcastId = mBroadcast.getLatestBroadcastId();
mMetricsFeatureProvider.action( if (broadcastId != -1) {
mContext, SettingsEnums.ACTION_AUDIO_SHARING_MAIN_SWITCH_OFF); mBroadcast.stopBroadcast(broadcastId);
mStoppingSharing.compareAndSet(false, true);
mSinksInAdding.clear();
mSinksToWaitFor.clear();
}
cleanUpStatesForStartSharing();
} }
} }
@@ -617,11 +653,22 @@ public class AudioSharingSwitchBarController extends BasePreferenceController
/* userTriggered= */ false, /* userTriggered= */ false,
/* deviceCountInSharing= */ targetActiveSinks.isEmpty() ? 0 : 1, /* deviceCountInSharing= */ targetActiveSinks.isEmpty() ? 0 : 1,
/* candidateDeviceCount= */ mDeviceItemsForSharing.size()); /* candidateDeviceCount= */ mDeviceItemsForSharing.size());
// Auto add primary/active sinks w/o user interactions.
if (!targetActiveSinks.isEmpty() && mTargetActiveItem != null) { if (!targetActiveSinks.isEmpty() && mTargetActiveItem != null) {
Log.d(TAG, "handleOnBroadcastReady: automatically add source to active sinks."); Log.d(TAG, "handleOnBroadcastReady: automatically add source to active sinks.");
addSourceToTargetSinks(targetActiveSinks, mTargetActiveItem.getName()); addSourceToTargetSinks(targetActiveSinks, mTargetActiveItem.getName());
// To avoid users advance to share then pair flow before the primary/active sinks
// successfully join the audio sharing, save the primary/active sinks in mSinksToWaitFor
// and popup dialog till adding source complete for these sinks.
if (mDeviceItemsForSharing.isEmpty()) {
mSinksToWaitFor.clear();
mSinksToWaitFor.addAll(targetActiveSinks);
}
mMetricsFeatureProvider.action(mContext, SettingsEnums.ACTION_AUTO_JOIN_AUDIO_SHARING); mMetricsFeatureProvider.action(mContext, SettingsEnums.ACTION_AUTO_JOIN_AUDIO_SHARING);
mTargetActiveItem = null; mTargetActiveItem = null;
// When audio sharing page is brought up by intent with EXTRA_START_LE_AUDIO_SHARING
// == true, plus there is one active lea headset and one connected lea headset, we
// should auto add these sinks without user interactions.
if (mIntentHandleStage.compareAndSet( if (mIntentHandleStage.compareAndSet(
StartIntentHandleStage.HANDLE_AUTO_ADD.ordinal(), StartIntentHandleStage.HANDLE_AUTO_ADD.ordinal(),
StartIntentHandleStage.HANDLED.ordinal()) StartIntentHandleStage.HANDLED.ordinal())
@@ -631,31 +678,42 @@ public class AudioSharingSwitchBarController extends BasePreferenceController
List<BluetoothDevice> targetSinks = mGroupedConnectedDevices.getOrDefault( List<BluetoothDevice> targetSinks = mGroupedConnectedDevices.getOrDefault(
target.getGroupId(), ImmutableList.of()); target.getGroupId(), ImmutableList.of());
addSourceToTargetSinks(targetSinks, target.getName()); addSourceToTargetSinks(targetSinks, target.getName());
cleanUp(); cleanUpStatesForStartSharing();
// TODO: Add metric for auto add by intent // TODO: Add metric for auto add by intent
return; return;
} }
} }
// Still mark intent as handled if early returned due to preconditions not met
mIntentHandleStage.compareAndSet( mIntentHandleStage.compareAndSet(
StartIntentHandleStage.HANDLE_AUTO_ADD.ordinal(), StartIntentHandleStage.HANDLE_AUTO_ADD.ordinal(),
StartIntentHandleStage.HANDLED.ordinal()); StartIntentHandleStage.HANDLED.ordinal());
if (mFragment == null) { if (mFragment == null) {
Log.d(TAG, "handleOnBroadcastReady: dialog fail to show due to null fragment."); Log.d(TAG, "handleOnBroadcastReady: dialog fail to show due to null fragment.");
// Clean up states before early return.
dismissLoadingStateDialogIfNeeded(); dismissLoadingStateDialogIfNeeded();
cleanUp(); cleanUpStatesForStartSharing();
return; return;
} }
showDialog(eventData); // To avoid users advance to share then pair flow before the primary/active sinks
// successfully join the audio sharing, popup dialog till adding source complete for
// mSinksToWaitFor.
if (mSinksToWaitFor.isEmpty() && !mStoppingSharing.get()) {
showAudioSharingDialog(eventData);
}
} }
private void showDialog(Pair<Integer, Object>[] eventData) { private void showAudioSharingDialog(Pair<Integer, Object>[] eventData) {
if (!BluetoothUtils.isBroadcasting(mBtManager)) {
Log.d(TAG, "Skip showAudioSharingDialog, broadcast is stopped");
return;
}
AudioSharingDialogFragment.DialogEventListener listener = AudioSharingDialogFragment.DialogEventListener listener =
new AudioSharingDialogFragment.DialogEventListener() { new AudioSharingDialogFragment.DialogEventListener() {
@Override @Override
public void onPositiveClick() { public void onPositiveClick() {
// Could go to other pages, dismiss the loading dialog. // Could go to other pages, dismiss the loading dialog.
dismissLoadingStateDialogIfNeeded(); dismissLoadingStateDialogIfNeeded();
cleanUp(); cleanUpStatesForStartSharing();
} }
@Override @Override
@@ -663,14 +721,14 @@ public class AudioSharingSwitchBarController extends BasePreferenceController
List<BluetoothDevice> targetSinks = mGroupedConnectedDevices.getOrDefault( List<BluetoothDevice> targetSinks = mGroupedConnectedDevices.getOrDefault(
item.getGroupId(), ImmutableList.of()); item.getGroupId(), ImmutableList.of());
addSourceToTargetSinks(targetSinks, item.getName()); addSourceToTargetSinks(targetSinks, item.getName());
cleanUp(); cleanUpStatesForStartSharing();
} }
@Override @Override
public void onCancelClick() { public void onCancelClick() {
// Could go to other pages, dismiss the loading dialog. // Could go to other pages, dismiss the loading dialog.
dismissLoadingStateDialogIfNeeded(); dismissLoadingStateDialogIfNeeded();
cleanUp(); cleanUpStatesForStartSharing();
} }
}; };
AudioSharingUtils.postOnMainThread( AudioSharingUtils.postOnMainThread(
@@ -684,6 +742,36 @@ public class AudioSharingSwitchBarController extends BasePreferenceController
}); });
} }
private void showRetryDialog() {
AudioSharingUtils.postOnMainThread(mContext,
() -> {
// Remove all opening dialogs before show retry dialog
dismissStaleDialogsOtherThanRetryDialog();
AudioSharingRetryDialogFragment.show(mFragment);
});
}
@UiThread
private void dismissStaleDialogsOtherThanRetryDialog() {
List<Fragment> fragments = new ArrayList<Fragment>();
try {
if (mFragment != null) {
fragments =
mFragment.getChildFragmentManager().getFragments();
}
} catch (Exception e) {
Log.e(TAG, "Fail to dismiss stale dialogs: " + e.getMessage());
}
for (Fragment fragment : fragments) {
if (fragment != null && fragment instanceof DialogFragment
&& !(fragment instanceof AudioSharingRetryDialogFragment)
&& ((DialogFragment) fragment).getDialog() != null) {
Log.d(TAG, "Remove stale dialog = " + fragment.getTag());
((DialogFragment) fragment).dismiss();
}
}
}
private static final class MainSwitchAccessibilityDelegate extends View.AccessibilityDelegate { private static final class MainSwitchAccessibilityDelegate extends View.AccessibilityDelegate {
@Override @Override
public boolean onRequestSendAccessibilityEvent( public boolean onRequestSendAccessibilityEvent(
@@ -742,10 +830,10 @@ public class AudioSharingSwitchBarController extends BasePreferenceController
private void addSourceToTargetSinks(List<BluetoothDevice> targetActiveSinks, private void addSourceToTargetSinks(List<BluetoothDevice> targetActiveSinks,
@NonNull String sinkName) { @NonNull String sinkName) {
mSinksInAdding.addAll(targetActiveSinks); mSinksInAdding.addAll(targetActiveSinks);
AudioSharingUtils.addSourceToTargetSinks(targetActiveSinks, mBtManager);
// TODO: move to res once finalized // TODO: move to res once finalized
String loadingMessage = "Sharing with " + sinkName + "..."; String loadingMessage = "Sharing with " + sinkName + "...";
showLoadingStateDialog(loadingMessage); showLoadingStateDialog(loadingMessage);
AudioSharingUtils.addSourceToTargetSinks(targetActiveSinks, mBtManager);
} }
private void showLoadingStateDialog(@NonNull String loadingMessage) { private void showLoadingStateDialog(@NonNull String loadingMessage) {
@@ -760,7 +848,7 @@ public class AudioSharingSwitchBarController extends BasePreferenceController
} }
} }
private void cleanUp() { private void cleanUpStatesForStartSharing() {
mGroupedConnectedDevices.clear(); mGroupedConnectedDevices.clear();
mDeviceItemsForSharing.clear(); mDeviceItemsForSharing.clear();
} }

View File

@@ -146,6 +146,7 @@ public class AudioSharingSwitchBarControllerTest {
@Mock private LocalBluetoothLeBroadcastAssistant mAssistant; @Mock private LocalBluetoothLeBroadcastAssistant mAssistant;
@Mock private VolumeControlProfile mVolumeControl; @Mock private VolumeControlProfile mVolumeControl;
@Mock private BluetoothLeBroadcastMetadata mMetadata; @Mock private BluetoothLeBroadcastMetadata mMetadata;
@Mock private BluetoothLeBroadcastReceiveState mState;
@Mock private CompoundButton mBtnView; @Mock private CompoundButton mBtnView;
@Mock private CachedBluetoothDevice mCachedDevice1; @Mock private CachedBluetoothDevice mCachedDevice1;
@Mock private CachedBluetoothDevice mCachedDevice2; @Mock private CachedBluetoothDevice mCachedDevice2;
@@ -519,10 +520,9 @@ public class AudioSharingSwitchBarControllerTest {
mContext, FeatureFlagUtils.SETTINGS_NEED_CONNECTED_BLE_DEVICE_FOR_BROADCAST, true); mContext, FeatureFlagUtils.SETTINGS_NEED_CONNECTED_BLE_DEVICE_FOR_BROADCAST, true);
when(mBtnView.isEnabled()).thenReturn(true); when(mBtnView.isEnabled()).thenReturn(true);
when(mAssistant.getAllConnectedDevices()).thenReturn(ImmutableList.of(mDevice2, mDevice1)); when(mAssistant.getAllConnectedDevices()).thenReturn(ImmutableList.of(mDevice2, mDevice1));
BluetoothLeBroadcastReceiveState state = mock(BluetoothLeBroadcastReceiveState.class); when(mState.getBroadcastId()).thenReturn(1);
when(state.getBroadcastId()).thenReturn(1);
when(mBroadcast.getLatestBroadcastId()).thenReturn(1); when(mBroadcast.getLatestBroadcastId()).thenReturn(1);
when(mAssistant.getAllSources(mDevice2)).thenReturn(ImmutableList.of(state)); when(mAssistant.getAllSources(mDevice2)).thenReturn(ImmutableList.of(mState));
when(mBroadcast.getLatestBluetoothLeBroadcastMetadata()).thenReturn(mMetadata); when(mBroadcast.getLatestBluetoothLeBroadcastMetadata()).thenReturn(mMetadata);
doNothing().when(mBroadcast).startPrivateBroadcast(); doNothing().when(mBroadcast).startPrivateBroadcast();
mController.onCheckedChanged(mBtnView, /* isChecked= */ true); mController.onCheckedChanged(mBtnView, /* isChecked= */ true);
@@ -549,14 +549,77 @@ public class AudioSharingSwitchBarControllerTest {
} }
@Test @Test
public void onPlaybackStarted_showJoinAudioSharingDialog() { public void onPlaybackStarted_singleActiveDevice_showJoinAudioSharingDialog() {
FeatureFlagUtils.setEnabled(
mContext, FeatureFlagUtils.SETTINGS_NEED_CONNECTED_BLE_DEVICE_FOR_BROADCAST, true);
when(mBtnView.isEnabled()).thenReturn(true);
when(mAssistant.getAllConnectedDevices()).thenReturn(ImmutableList.of(mDevice2));
when(mAssistant.getAllSources(any(BluetoothDevice.class))).thenReturn(ImmutableList.of());
doNothing().when(mBroadcast).startPrivateBroadcast();
mController.onCheckedChanged(mBtnView, /* isChecked= */ true);
shadowOf(Looper.getMainLooper()).idle();
verify(mBroadcast).startPrivateBroadcast();
List<Fragment> childFragments = mParentFragment.getChildFragmentManager().getFragments();
assertThat(childFragments).comparingElementsUsing(CLAZZNAME_EQUALS).containsExactly(
AudioSharingLoadingStateDialogFragment.class.getName());
when(mBroadcast.isEnabled(null)).thenReturn(true);
when(mBroadcast.getLatestBluetoothLeBroadcastMetadata()).thenReturn(mMetadata);
mController.mBroadcastCallback.onPlaybackStarted(0, 0);
shadowOf(Looper.getMainLooper()).idle();
verify(mFeatureFactory.metricsFeatureProvider)
.action(any(Context.class), eq(SettingsEnums.ACTION_AUTO_JOIN_AUDIO_SHARING));
when(mState.getBisSyncState()).thenReturn(ImmutableList.of(1L));
mController.mBroadcastAssistantCallback.onReceiveStateChanged(mDevice2, /* sourceId= */ 1,
mState);
shadowOf(Looper.getMainLooper()).idle();
childFragments = mParentFragment.getChildFragmentManager().getFragments();
assertThat(childFragments)
.comparingElementsUsing(CLAZZNAME_EQUALS)
.containsExactly(AudioSharingDialogFragment.class.getName());
Pair<Integer, Object>[] eventData = new Pair[0];
for (Fragment fragment : childFragments) {
if (fragment instanceof AudioSharingDialogFragment) {
eventData = ((AudioSharingDialogFragment) fragment).getEventData();
break;
}
}
assertThat(eventData)
.asList()
.containsExactly(
Pair.create(
AudioSharingUtils.MetricKey.METRIC_KEY_SOURCE_PAGE_ID.ordinal(),
SettingsEnums.AUDIO_SHARING_SETTINGS),
Pair.create(
AudioSharingUtils.MetricKey.METRIC_KEY_PAGE_ID.ordinal(),
SettingsEnums.DIALOG_AUDIO_SHARING_ADD_DEVICE),
Pair.create(
AudioSharingUtils.MetricKey.METRIC_KEY_USER_TRIGGERED.ordinal(), 0),
Pair.create(
AudioSharingUtils.MetricKey.METRIC_KEY_DEVICE_COUNT_IN_SHARING
.ordinal(),
1),
Pair.create(
AudioSharingUtils.MetricKey.METRIC_KEY_CANDIDATE_DEVICE_COUNT
.ordinal(),
0));
childFragments.forEach(fragment -> ((DialogFragment) fragment).dismiss());
}
@Test
public void onPlaybackStarted_oneActiveOnConnected_showJoinAudioSharingDialog() {
FeatureFlagUtils.setEnabled( FeatureFlagUtils.setEnabled(
mContext, FeatureFlagUtils.SETTINGS_NEED_CONNECTED_BLE_DEVICE_FOR_BROADCAST, true); mContext, FeatureFlagUtils.SETTINGS_NEED_CONNECTED_BLE_DEVICE_FOR_BROADCAST, true);
when(mBtnView.isEnabled()).thenReturn(true); when(mBtnView.isEnabled()).thenReturn(true);
when(mAssistant.getAllConnectedDevices()).thenReturn(ImmutableList.of(mDevice2, mDevice1)); when(mAssistant.getAllConnectedDevices()).thenReturn(ImmutableList.of(mDevice2, mDevice1));
when(mAssistant.getAllSources(any(BluetoothDevice.class))).thenReturn(ImmutableList.of()); when(mAssistant.getAllSources(any(BluetoothDevice.class))).thenReturn(ImmutableList.of());
doNothing().when(mBroadcast).startPrivateBroadcast(); doNothing().when(mBroadcast).startPrivateBroadcast();
when(mBroadcast.getLatestBluetoothLeBroadcastMetadata()).thenReturn(mMetadata);
mController.onCheckedChanged(mBtnView, /* isChecked= */ true); mController.onCheckedChanged(mBtnView, /* isChecked= */ true);
shadowOf(Looper.getMainLooper()).idle(); shadowOf(Looper.getMainLooper()).idle();
@@ -570,6 +633,8 @@ public class AudioSharingSwitchBarControllerTest {
String expectedMessage = "Starting audio stream..."; String expectedMessage = "Starting audio stream...";
checkLoadingStateDialogMessage(loadingFragment, expectedMessage); checkLoadingStateDialogMessage(loadingFragment, expectedMessage);
when(mBroadcast.isEnabled(null)).thenReturn(true);
when(mBroadcast.getLatestBluetoothLeBroadcastMetadata()).thenReturn(mMetadata);
mController.mBroadcastCallback.onPlaybackStarted(0, 0); mController.mBroadcastCallback.onPlaybackStarted(0, 0);
shadowOf(Looper.getMainLooper()).idle(); shadowOf(Looper.getMainLooper()).idle();
@@ -616,18 +681,19 @@ public class AudioSharingSwitchBarControllerTest {
} }
@Test @Test
public void onPlaybackStarted_clickShareBtnOnDialog_addSource() { public void onPlaybackStarted_oneActiveOnConnected_clickShareBtnOnDialog_addSource() {
FeatureFlagUtils.setEnabled( FeatureFlagUtils.setEnabled(
mContext, FeatureFlagUtils.SETTINGS_NEED_CONNECTED_BLE_DEVICE_FOR_BROADCAST, true); mContext, FeatureFlagUtils.SETTINGS_NEED_CONNECTED_BLE_DEVICE_FOR_BROADCAST, true);
when(mBtnView.isEnabled()).thenReturn(true); when(mBtnView.isEnabled()).thenReturn(true);
when(mAssistant.getAllConnectedDevices()).thenReturn(ImmutableList.of(mDevice2, mDevice1)); when(mAssistant.getAllConnectedDevices()).thenReturn(ImmutableList.of(mDevice2, mDevice1));
when(mAssistant.getAllSources(any(BluetoothDevice.class))).thenReturn(ImmutableList.of()); when(mAssistant.getAllSources(any(BluetoothDevice.class))).thenReturn(ImmutableList.of());
when(mBroadcast.getLatestBluetoothLeBroadcastMetadata()).thenReturn(mMetadata);
doNothing().when(mBroadcast).startPrivateBroadcast(); doNothing().when(mBroadcast).startPrivateBroadcast();
mController.onCheckedChanged(mBtnView, /* isChecked= */ true); mController.onCheckedChanged(mBtnView, /* isChecked= */ true);
shadowOf(Looper.getMainLooper()).idle(); shadowOf(Looper.getMainLooper()).idle();
verify(mBroadcast).startPrivateBroadcast(); verify(mBroadcast).startPrivateBroadcast();
when(mBroadcast.isEnabled(null)).thenReturn(true);
when(mBroadcast.getLatestBluetoothLeBroadcastMetadata()).thenReturn(mMetadata);
mController.mBroadcastCallback.onPlaybackStarted(0, 0); mController.mBroadcastCallback.onPlaybackStarted(0, 0);
shadowOf(Looper.getMainLooper()).idle(); shadowOf(Looper.getMainLooper()).idle();
@@ -656,18 +722,19 @@ public class AudioSharingSwitchBarControllerTest {
} }
@Test @Test
public void onPlaybackStarted_clickCancelBtnOnDialog_doNothing() { public void onPlaybackStarted_oneActiveOnConnected_clickCancelBtnOnDialog_doNothing() {
FeatureFlagUtils.setEnabled( FeatureFlagUtils.setEnabled(
mContext, FeatureFlagUtils.SETTINGS_NEED_CONNECTED_BLE_DEVICE_FOR_BROADCAST, true); mContext, FeatureFlagUtils.SETTINGS_NEED_CONNECTED_BLE_DEVICE_FOR_BROADCAST, true);
when(mBtnView.isEnabled()).thenReturn(true); when(mBtnView.isEnabled()).thenReturn(true);
when(mAssistant.getAllConnectedDevices()).thenReturn(ImmutableList.of(mDevice2, mDevice1)); when(mAssistant.getAllConnectedDevices()).thenReturn(ImmutableList.of(mDevice2, mDevice1));
when(mAssistant.getAllSources(any(BluetoothDevice.class))).thenReturn(ImmutableList.of()); when(mAssistant.getAllSources(any(BluetoothDevice.class))).thenReturn(ImmutableList.of());
when(mBroadcast.getLatestBluetoothLeBroadcastMetadata()).thenReturn(mMetadata);
doNothing().when(mBroadcast).startPrivateBroadcast(); doNothing().when(mBroadcast).startPrivateBroadcast();
mController.onCheckedChanged(mBtnView, /* isChecked= */ true); mController.onCheckedChanged(mBtnView, /* isChecked= */ true);
shadowOf(Looper.getMainLooper()).idle(); shadowOf(Looper.getMainLooper()).idle();
verify(mBroadcast).startPrivateBroadcast(); verify(mBroadcast).startPrivateBroadcast();
when(mBroadcast.isEnabled(null)).thenReturn(true);
when(mBroadcast.getLatestBluetoothLeBroadcastMetadata()).thenReturn(mMetadata);
mController.mBroadcastCallback.onPlaybackStarted(0, 0); mController.mBroadcastCallback.onPlaybackStarted(0, 0);
shadowOf(Looper.getMainLooper()).idle(); shadowOf(Looper.getMainLooper()).idle();
@@ -754,14 +821,50 @@ public class AudioSharingSwitchBarControllerTest {
} }
@Test @Test
public void testAssistantCallbacks_onSourceAddFailed_logAction() { public void testAssistantCallbacks_onSourceAddFailed_twoDevices_showRetryAndLogAction() {
FeatureFlagUtils.setEnabled(
mContext, FeatureFlagUtils.SETTINGS_NEED_CONNECTED_BLE_DEVICE_FOR_BROADCAST, true);
when(mBtnView.isEnabled()).thenReturn(true);
when(mAssistant.getAllConnectedDevices()).thenReturn(ImmutableList.of(mDevice2, mDevice1));
when(mAssistant.getAllSources(any(BluetoothDevice.class))).thenReturn(ImmutableList.of());
doNothing().when(mBroadcast).startPrivateBroadcast();
mController.onCheckedChanged(mBtnView, /* isChecked= */ true);
shadowOf(Looper.getMainLooper()).idle();
verify(mBroadcast).startPrivateBroadcast();
when(mBroadcast.isEnabled(null)).thenReturn(true);
when(mBroadcast.getLatestBluetoothLeBroadcastMetadata()).thenReturn(mMetadata);
mController.mBroadcastCallback.onPlaybackStarted(0, 0);
shadowOf(Looper.getMainLooper()).idle();
verify(mAssistant).addSource(mDevice2, mMetadata, /* isGroupOp= */ false);
AlertDialog dialog = ShadowAlertDialogCompat.getLatestAlertDialog();
assertThat(dialog).isNotNull();
View btnView = dialog.findViewById(R.id.positive_btn);
assertThat(btnView).isNotNull();
btnView.performClick();
shadowMainLooper().idle();
verify(mAssistant).addSource(mDevice1, mMetadata, /* isGroupOp= */ false);
assertThat(dialog.isShowing()).isFalse();
mController.mBroadcastAssistantCallback.onSourceAddFailed( mController.mBroadcastAssistantCallback.onSourceAddFailed(
mDevice1, mMetadata, /* reason= */ 1); mDevice1, mMetadata, /* reason= */ 1);
shadowMainLooper().idle();
// Loading state dialog shows sharing state for the user chosen sink.
List<Fragment> childFragments = mParentFragment.getChildFragmentManager().getFragments();
assertThat(childFragments).comparingElementsUsing(CLAZZNAME_EQUALS).containsExactly(
AudioSharingRetryDialogFragment.class.getName());
verify(mFeatureFactory.metricsFeatureProvider) verify(mFeatureFactory.metricsFeatureProvider)
.action( .action(
mContext, mContext,
SettingsEnums.ACTION_AUDIO_SHARING_JOIN_FAILED, SettingsEnums.ACTION_AUDIO_SHARING_JOIN_FAILED,
SettingsEnums.AUDIO_SHARING_SETTINGS); SettingsEnums.AUDIO_SHARING_SETTINGS);
childFragments.forEach(fragment -> ((DialogFragment) fragment).dismiss());
} }
@Test @Test
@@ -772,10 +875,9 @@ public class AudioSharingSwitchBarControllerTest {
assertThat(childFragments).comparingElementsUsing(CLAZZNAME_EQUALS).containsExactly( assertThat(childFragments).comparingElementsUsing(CLAZZNAME_EQUALS).containsExactly(
AudioSharingLoadingStateDialogFragment.class.getName()); AudioSharingLoadingStateDialogFragment.class.getName());
BluetoothLeBroadcastReceiveState state = mock(BluetoothLeBroadcastReceiveState.class); when(mState.getBisSyncState()).thenReturn(ImmutableList.of(1L));
when(state.getBisSyncState()).thenReturn(ImmutableList.of(1L));
mController.mBroadcastAssistantCallback.onReceiveStateChanged(mDevice1, /* sourceId= */ 1, mController.mBroadcastAssistantCallback.onReceiveStateChanged(mDevice1, /* sourceId= */ 1,
state); mState);
shadowOf(Looper.getMainLooper()).idle(); shadowOf(Looper.getMainLooper()).idle();
childFragments = mParentFragment.getChildFragmentManager().getFragments(); childFragments = mParentFragment.getChildFragmentManager().getFragments();
assertThat(childFragments).isEmpty(); assertThat(childFragments).isEmpty();
@@ -783,11 +885,9 @@ public class AudioSharingSwitchBarControllerTest {
@Test @Test
public void testAssistantCallbacks_doNothing() { public void testAssistantCallbacks_doNothing() {
BluetoothLeBroadcastReceiveState state = mock(BluetoothLeBroadcastReceiveState.class);
// Do nothing // Do nothing
mController.mBroadcastAssistantCallback.onReceiveStateChanged( mController.mBroadcastAssistantCallback.onReceiveStateChanged(
mDevice1, /* sourceId= */ 1, state); mDevice1, /* sourceId= */ 1, mState);
mController.mBroadcastAssistantCallback.onSearchStarted(/* reason= */ 1); mController.mBroadcastAssistantCallback.onSearchStarted(/* reason= */ 1);
mController.mBroadcastAssistantCallback.onSearchStartFailed(/* reason= */ 1); mController.mBroadcastAssistantCallback.onSearchStartFailed(/* reason= */ 1);
mController.mBroadcastAssistantCallback.onSearchStopped(/* reason= */ 1); mController.mBroadcastAssistantCallback.onSearchStopped(/* reason= */ 1);