[Audiosharing] Show loading state on audio sharing page

1. Show "Sharing audio stream..." once starting audio sharing
2. Show "Sharing with <devicename>..." once adding source to the device

Test: atest
Flag: com.android.settingslib.flags.enable_le_audio_sharing
Bug: 362858894
Change-Id: I6c19d999baaa91d6a5365f24e88efe79c2b38072
This commit is contained in:
Yiyi Shen
2024-09-11 19:21:53 +08:00
parent 0e7d5fcf0d
commit a6d9fa163e
4 changed files with 201 additions and 53 deletions

View File

@@ -44,7 +44,7 @@ public class AudioSharingLoadingStateDialogFragment extends InstrumentedDialogFr
private static final String TAG = "AudioSharingLoadingDlg"; private static final String TAG = "AudioSharingLoadingDlg";
private static final String BUNDLE_KEY_MESSAGE = "bundle_key_message"; private static final String BUNDLE_KEY_MESSAGE = "bundle_key_message";
private static final long AUTO_DISMISS_TIME_THRESHOLD_MS = TimeUnit.SECONDS.toMillis(10); private static final long AUTO_DISMISS_TIME_THRESHOLD_MS = TimeUnit.SECONDS.toMillis(15);
private static final int AUTO_DISMISS_MESSAGE_ID = R.id.message; private static final int AUTO_DISMISS_MESSAGE_ID = R.id.message;
private static String sMessage = ""; private static String sMessage = "";
@@ -74,14 +74,16 @@ public class AudioSharingLoadingStateDialogFragment extends InstrumentedDialogFr
} }
AlertDialog dialog = AudioSharingDialogHelper.getDialogIfShowing(manager, TAG); AlertDialog dialog = AudioSharingDialogHelper.getDialogIfShowing(manager, TAG);
if (dialog != null) { if (dialog != null) {
if (sMessage.equals(message)) { if (!sMessage.equals(message)) {
Log.d(TAG, "Dialog is showing with same message, return."); Log.d(TAG, "Update dialog message.");
return; TextView messageView = dialog.findViewById(R.id.message);
} else { if (messageView != null) {
Log.d(TAG, "Dialog is showing with different message, dismiss and reshow."); messageView.setText(message);
dialog.dismiss();
} }
} }
Log.d(TAG, "Dialog is showing, return.");
return;
}
sMessage = message; sMessage = message;
Log.d(TAG, "Show up the loading dialog."); Log.d(TAG, "Show up the loading dialog.");
Bundle args = new Bundle(); Bundle args = new Bundle();
@@ -113,8 +115,10 @@ public class AudioSharingLoadingStateDialogFragment extends InstrumentedDialogFr
@NonNull @NonNull
public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) { public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
mHandler = new Handler(Looper.getMainLooper()); mHandler = new Handler(Looper.getMainLooper());
mHandler.postDelayed(() -> dismiss(), AUTO_DISMISS_MESSAGE_ID, mHandler.postDelayed(() -> {
AUTO_DISMISS_TIME_THRESHOLD_MS); Log.d(TAG, "Auto dismiss dialog after timeout");
dismiss();
}, AUTO_DISMISS_MESSAGE_ID, AUTO_DISMISS_TIME_THRESHOLD_MS);
Bundle args = requireArguments(); Bundle args = requireArguments();
String message = args.getString(BUNDLE_KEY_MESSAGE, ""); String message = args.getString(BUNDLE_KEY_MESSAGE, "");
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
@@ -132,6 +136,7 @@ public class AudioSharingLoadingStateDialogFragment extends InstrumentedDialogFr
public void onDismiss(@NonNull DialogInterface dialog) { public void onDismiss(@NonNull DialogInterface dialog) {
super.onDismiss(dialog); super.onDismiss(dialog);
if (mHandler != null) { if (mHandler != null) {
Log.d(TAG, "Dialog dismissed, remove auto dismiss task");
mHandler.removeMessages(AUTO_DISMISS_MESSAGE_ID); mHandler.removeMessages(AUTO_DISMISS_MESSAGE_ID);
} }
} }

View File

@@ -70,6 +70,7 @@ import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
import java.util.Map; import java.util.Map;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
import java.util.concurrent.Executors; import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicBoolean;
@@ -112,12 +113,13 @@ public class AudioSharingSwitchBarController extends BasePreferenceController
private final MetricsFeatureProvider mMetricsFeatureProvider; private final MetricsFeatureProvider mMetricsFeatureProvider;
private final OnAudioSharingStateChangedListener mListener; private final OnAudioSharingStateChangedListener mListener;
private Map<Integer, List<BluetoothDevice>> mGroupedConnectedDevices = new HashMap<>(); private Map<Integer, List<BluetoothDevice>> mGroupedConnectedDevices = new HashMap<>();
private List<BluetoothDevice> mTargetActiveSinks = new ArrayList<>(); @Nullable private AudioSharingDeviceItem mTargetActiveItem;
private List<AudioSharingDeviceItem> mDeviceItemsForSharing = new ArrayList<>(); private List<AudioSharingDeviceItem> mDeviceItemsForSharing = new ArrayList<>();
@VisibleForTesting IntentFilter mIntentFilter; @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());
private CopyOnWriteArrayList<BluetoothDevice> mSinksInAdding = new CopyOnWriteArrayList<>();
@VisibleForTesting @VisibleForTesting
BroadcastReceiver mReceiver = BroadcastReceiver mReceiver =
@@ -294,7 +296,16 @@ public class AudioSharingSwitchBarController extends BasePreferenceController
public void onReceiveStateChanged( public void onReceiveStateChanged(
@NonNull BluetoothDevice sink, @NonNull BluetoothDevice sink,
int sourceId, int sourceId,
@NonNull BluetoothLeBroadcastReceiveState state) {} @NonNull BluetoothLeBroadcastReceiveState state) {
if (BluetoothUtils.isConnected(state)) {
if (mSinksInAdding.contains(sink)) {
mSinksInAdding.remove(sink);
}
dismissLoadingStateDialogIfNeeded();
Log.d(TAG, "onReceiveStateChanged() connected, sink = " + sink
+ ", remaining sinks = " + mSinksInAdding);
}
}
}; };
AudioSharingSwitchBarController( AudioSharingSwitchBarController(
@@ -506,17 +517,20 @@ public class AudioSharingSwitchBarController extends BasePreferenceController
mBtManager, mGroupedConnectedDevices, /* filterByInSharing= */ false); mBtManager, mGroupedConnectedDevices, /* filterByInSharing= */ false);
// deviceItems is ordered. The active device is the first place if exits. // deviceItems is ordered. The active device is the first place if exits.
mDeviceItemsForSharing = new ArrayList<>(deviceItems); mDeviceItemsForSharing = new ArrayList<>(deviceItems);
mTargetActiveSinks = new ArrayList<>(); mTargetActiveItem = null;
if (!deviceItems.isEmpty() && deviceItems.get(0).isActive()) { if (!deviceItems.isEmpty() && deviceItems.get(0).isActive()) {
// If active device exists for audio sharing, share to it // If active device exists for audio sharing, share to it
// automatically once the broadcast is started. // automatically once the broadcast is started.
mTargetActiveSinks = mTargetActiveItem = deviceItems.get(0);
mGroupedConnectedDevices.getOrDefault(
deviceItems.get(0).getGroupId(), ImmutableList.of());
mDeviceItemsForSharing.remove(0); mDeviceItemsForSharing.remove(0);
} }
if (mBroadcast != null) { if (mBroadcast != null) {
mBroadcast.startPrivateBroadcast(); mBroadcast.startPrivateBroadcast();
mSinksInAdding.clear();
// TODO: use string res once finalized.
AudioSharingUtils.postOnMainThread(mContext,
() -> AudioSharingLoadingStateDialogFragment.show(mFragment,
"Starting audio stream..."));
mMetricsFeatureProvider.action( mMetricsFeatureProvider.action(
mContext, mContext,
SettingsEnums.ACTION_AUDIO_SHARING_MAIN_SWITCH_ON, SettingsEnums.ACTION_AUDIO_SHARING_MAIN_SWITCH_ON,
@@ -580,27 +594,30 @@ public class AudioSharingSwitchBarController extends BasePreferenceController
} }
private void handleOnBroadcastReady() { private void handleOnBroadcastReady() {
List<BluetoothDevice> targetActiveSinks = mTargetActiveItem == null ? ImmutableList.of()
: mGroupedConnectedDevices.getOrDefault(
mTargetActiveItem.getGroupId(), ImmutableList.of());
Pair<Integer, Object>[] eventData = Pair<Integer, Object>[] eventData =
AudioSharingUtils.buildAudioSharingDialogEventData( AudioSharingUtils.buildAudioSharingDialogEventData(
SettingsEnums.AUDIO_SHARING_SETTINGS, SettingsEnums.AUDIO_SHARING_SETTINGS,
SettingsEnums.DIALOG_AUDIO_SHARING_ADD_DEVICE, SettingsEnums.DIALOG_AUDIO_SHARING_ADD_DEVICE,
/* userTriggered= */ false, /* userTriggered= */ false,
/* deviceCountInSharing= */ mTargetActiveSinks.isEmpty() ? 0 : 1, /* deviceCountInSharing= */ targetActiveSinks.isEmpty() ? 0 : 1,
/* candidateDeviceCount= */ mDeviceItemsForSharing.size()); /* candidateDeviceCount= */ mDeviceItemsForSharing.size());
if (!mTargetActiveSinks.isEmpty()) { 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.");
AudioSharingUtils.addSourceToTargetSinks(mTargetActiveSinks, mBtManager); addSourceToTargetSinks(targetActiveSinks, mTargetActiveItem.getName());
mMetricsFeatureProvider.action(mContext, SettingsEnums.ACTION_AUTO_JOIN_AUDIO_SHARING); mMetricsFeatureProvider.action(mContext, SettingsEnums.ACTION_AUTO_JOIN_AUDIO_SHARING);
mTargetActiveSinks.clear(); mTargetActiveItem = null;
if (mIntentHandleStage.compareAndSet( if (mIntentHandleStage.compareAndSet(
StartIntentHandleStage.HANDLE_AUTO_ADD.ordinal(), StartIntentHandleStage.HANDLE_AUTO_ADD.ordinal(),
StartIntentHandleStage.HANDLED.ordinal()) StartIntentHandleStage.HANDLED.ordinal())
&& mDeviceItemsForSharing.size() == 1) { && mDeviceItemsForSharing.size() == 1) {
Log.d(TAG, "handleOnBroadcastReady: auto add source to the second device"); Log.d(TAG, "handleOnBroadcastReady: auto add source to the second device");
AudioSharingUtils.addSourceToTargetSinks( AudioSharingDeviceItem target = mDeviceItemsForSharing.get(0);
mGroupedConnectedDevices.getOrDefault( List<BluetoothDevice> targetSinks = mGroupedConnectedDevices.getOrDefault(
mDeviceItemsForSharing.get(0).getGroupId(), ImmutableList.of()), target.getGroupId(), ImmutableList.of());
mBtManager); addSourceToTargetSinks(targetSinks, target.getName());
cleanUp(); cleanUp();
// TODO: Add metric for auto add by intent // TODO: Add metric for auto add by intent
return; return;
@@ -611,6 +628,7 @@ public class AudioSharingSwitchBarController extends BasePreferenceController
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.");
dismissLoadingStateDialogIfNeeded();
cleanUp(); cleanUp();
return; return;
} }
@@ -622,15 +640,15 @@ public class AudioSharingSwitchBarController extends BasePreferenceController
new AudioSharingDialogFragment.DialogEventListener() { new AudioSharingDialogFragment.DialogEventListener() {
@Override @Override
public void onItemClick(@NonNull AudioSharingDeviceItem item) { public void onItemClick(@NonNull AudioSharingDeviceItem item) {
AudioSharingUtils.addSourceToTargetSinks( List<BluetoothDevice> targetSinks = mGroupedConnectedDevices.getOrDefault(
mGroupedConnectedDevices.getOrDefault( item.getGroupId(), ImmutableList.of());
item.getGroupId(), ImmutableList.of()), addSourceToTargetSinks(targetSinks, item.getName());
mBtManager);
cleanUp(); cleanUp();
} }
@Override @Override
public void onCancelClick() { public void onCancelClick() {
dismissLoadingStateDialogIfNeeded();
cleanUp(); cleanUp();
} }
}; };
@@ -700,6 +718,27 @@ public class AudioSharingSwitchBarController extends BasePreferenceController
}); });
} }
private void addSourceToTargetSinks(List<BluetoothDevice> targetActiveSinks,
@NonNull String sinkName) {
mSinksInAdding.addAll(targetActiveSinks);
AudioSharingUtils.addSourceToTargetSinks(targetActiveSinks, mBtManager);
// TODO: move to res once finalized
String loadingMessage = "Sharing with " + sinkName + "...";
showLoadingStateDialog(loadingMessage);
}
private void showLoadingStateDialog(@NonNull String loadingMessage) {
AudioSharingUtils.postOnMainThread(mContext,
() -> AudioSharingLoadingStateDialogFragment.show(mFragment, loadingMessage));
}
private void dismissLoadingStateDialogIfNeeded() {
if (mSinksInAdding.isEmpty()) {
AudioSharingUtils.postOnMainThread(mContext,
() -> AudioSharingLoadingStateDialogFragment.dismiss(mFragment));
}
}
private void cleanUp() { private void cleanUp() {
mGroupedConnectedDevices.clear(); mGroupedConnectedDevices.clear();
mDeviceItemsForSharing.clear(); mDeviceItemsForSharing.clear();

View File

@@ -150,7 +150,7 @@ public class AudioSharingLoadingStateDialogFragmentTest {
} }
@Test @Test
public void showDialog_newMessage_dismissAndShowNewDialog() { public void showDialog_newMessage_keepAndUpdateDialog() {
mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING); mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
AudioSharingLoadingStateDialogFragment.show(mParent, TEST_MESSAGE1); AudioSharingLoadingStateDialogFragment.show(mParent, TEST_MESSAGE1);
shadowMainLooper().idle(); shadowMainLooper().idle();
@@ -163,12 +163,7 @@ public class AudioSharingLoadingStateDialogFragmentTest {
AudioSharingLoadingStateDialogFragment.show(mParent, TEST_MESSAGE2); AudioSharingLoadingStateDialogFragment.show(mParent, TEST_MESSAGE2);
shadowMainLooper().idle(); shadowMainLooper().idle();
assertThat(dialog.isShowing()).isFalse(); assertThat(dialog.isShowing()).isTrue();
AlertDialog newDialog = ShadowAlertDialogCompat.getLatestAlertDialog();
assertThat(newDialog).isNotNull();
assertThat(newDialog.isShowing()).isTrue();
view = newDialog.findViewById(R.id.message);
assertThat(view).isNotNull();
assertThat(view.getText().toString()).isEqualTo(TEST_MESSAGE2); assertThat(view.getText().toString()).isEqualTo(TEST_MESSAGE2);
} }
} }

View File

@@ -57,7 +57,9 @@ import android.util.Pair;
import android.view.View; import android.view.View;
import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityEvent;
import android.widget.CompoundButton; import android.widget.CompoundButton;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AlertDialog; import androidx.appcompat.app.AlertDialog;
import androidx.fragment.app.DialogFragment; import androidx.fragment.app.DialogFragment;
import androidx.fragment.app.Fragment; import androidx.fragment.app.Fragment;
@@ -235,6 +237,7 @@ public class AudioSharingSwitchBarControllerTest {
@After @After
public void tearDown() { public void tearDown() {
ShadowAlertDialogCompat.reset();
ShadowBluetoothUtils.reset(); ShadowBluetoothUtils.reset();
ShadowThreadUtils.reset(); ShadowThreadUtils.reset();
} }
@@ -426,6 +429,8 @@ public class AudioSharingSwitchBarControllerTest {
assertThat(childFragments) assertThat(childFragments)
.comparingElementsUsing(CLAZZNAME_EQUALS) .comparingElementsUsing(CLAZZNAME_EQUALS)
.containsExactly(AudioSharingConfirmDialogFragment.class.getName()); .containsExactly(AudioSharingConfirmDialogFragment.class.getName());
childFragments.forEach(fragment -> ((DialogFragment) fragment).dismiss());
} }
@Test @Test
@@ -490,14 +495,21 @@ public class AudioSharingSwitchBarControllerTest {
public void onAudioSharingProfilesConnected() {} public void onAudioSharingProfilesConnected() {}
}); });
mController.onCheckedChanged(mBtnView, /* isChecked= */ true); mController.onCheckedChanged(mBtnView, /* isChecked= */ true);
shadowOf(Looper.getMainLooper()).idle();
verify(mBroadcast).startPrivateBroadcast(); verify(mBroadcast).startPrivateBroadcast();
List<Fragment> childFragments = mParentFragment.getChildFragmentManager().getFragments();
// No loading state dialog.
assertThat(childFragments).isEmpty();
mController.mBroadcastCallback.onPlaybackStarted(0, 0); mController.mBroadcastCallback.onPlaybackStarted(0, 0);
shadowOf(Looper.getMainLooper()).idle(); shadowOf(Looper.getMainLooper()).idle();
verify(mFeatureFactory.metricsFeatureProvider) verify(mFeatureFactory.metricsFeatureProvider)
.action(any(Context.class), eq(SettingsEnums.ACTION_AUTO_JOIN_AUDIO_SHARING)); .action(any(Context.class), eq(SettingsEnums.ACTION_AUTO_JOIN_AUDIO_SHARING));
List<Fragment> childFragments = mParentFragment.getChildFragmentManager().getFragments(); childFragments = mParentFragment.getChildFragmentManager().getFragments();
// No audio sharing dialog.
assertThat(childFragments).isEmpty(); assertThat(childFragments).isEmpty();
} }
@@ -514,7 +526,13 @@ public class AudioSharingSwitchBarControllerTest {
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);
shadowOf(Looper.getMainLooper()).idle();
verify(mBroadcast).startPrivateBroadcast(); verify(mBroadcast).startPrivateBroadcast();
List<Fragment> childFragments = mParentFragment.getChildFragmentManager().getFragments();
assertThat(childFragments).comparingElementsUsing(CLAZZNAME_EQUALS).containsExactly(
AudioSharingLoadingStateDialogFragment.class.getName());
mController.mBroadcastCallback.onPlaybackStarted(0, 0); mController.mBroadcastCallback.onPlaybackStarted(0, 0);
shadowOf(Looper.getMainLooper()).idle(); shadowOf(Looper.getMainLooper()).idle();
@@ -522,8 +540,12 @@ public class AudioSharingSwitchBarControllerTest {
verify(mFeatureFactory.metricsFeatureProvider, never()) verify(mFeatureFactory.metricsFeatureProvider, never())
.action(any(Context.class), eq(SettingsEnums.ACTION_AUTO_JOIN_AUDIO_SHARING)); .action(any(Context.class), eq(SettingsEnums.ACTION_AUTO_JOIN_AUDIO_SHARING));
List<Fragment> childFragments = mParentFragment.getChildFragmentManager().getFragments(); childFragments = mParentFragment.getChildFragmentManager().getFragments();
assertThat(childFragments).isEmpty(); // No audio sharing dialog.
assertThat(childFragments).comparingElementsUsing(CLAZZNAME_EQUALS).doesNotContain(
AudioSharingDialogFragment.class.getName());
childFragments.forEach(fragment -> ((DialogFragment) fragment).dismiss());
} }
@Test @Test
@@ -534,23 +556,42 @@ public class AudioSharingSwitchBarControllerTest {
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();
mController.onCheckedChanged(mBtnView, /* isChecked= */ true);
when(mBroadcast.getLatestBluetoothLeBroadcastMetadata()).thenReturn(mMetadata); when(mBroadcast.getLatestBluetoothLeBroadcastMetadata()).thenReturn(mMetadata);
mController.onCheckedChanged(mBtnView, /* isChecked= */ true);
shadowOf(Looper.getMainLooper()).idle();
verify(mBroadcast).startPrivateBroadcast(); verify(mBroadcast).startPrivateBroadcast();
List<Fragment> childFragments = mParentFragment.getChildFragmentManager().getFragments();
assertThat(childFragments).comparingElementsUsing(CLAZZNAME_EQUALS).containsExactly(
AudioSharingLoadingStateDialogFragment.class.getName());
AudioSharingLoadingStateDialogFragment loadingFragment =
(AudioSharingLoadingStateDialogFragment) Iterables.getOnlyElement(childFragments);
// TODO: use string res once finalized
String expectedMessage = "Starting audio stream...";
checkLoadingStateDialogMessage(loadingFragment, expectedMessage);
mController.mBroadcastCallback.onPlaybackStarted(0, 0); mController.mBroadcastCallback.onPlaybackStarted(0, 0);
shadowOf(Looper.getMainLooper()).idle(); shadowOf(Looper.getMainLooper()).idle();
verify(mFeatureFactory.metricsFeatureProvider) verify(mFeatureFactory.metricsFeatureProvider)
.action(any(Context.class), eq(SettingsEnums.ACTION_AUTO_JOIN_AUDIO_SHARING)); .action(any(Context.class), eq(SettingsEnums.ACTION_AUTO_JOIN_AUDIO_SHARING));
// TODO: use string res once finalized
expectedMessage = "Sharing with " + TEST_DEVICE_NAME2 + "...";
checkLoadingStateDialogMessage(loadingFragment, expectedMessage);
List<Fragment> childFragments = mParentFragment.getChildFragmentManager().getFragments(); childFragments = mParentFragment.getChildFragmentManager().getFragments();
assertThat(childFragments) assertThat(childFragments)
.comparingElementsUsing(CLAZZNAME_EQUALS) .comparingElementsUsing(CLAZZNAME_EQUALS)
.containsExactly(AudioSharingDialogFragment.class.getName()); .containsExactly(AudioSharingDialogFragment.class.getName(),
AudioSharingLoadingStateDialogFragment.class.getName());
AudioSharingDialogFragment fragment = Pair<Integer, Object>[] eventData = new Pair[0];
(AudioSharingDialogFragment) Iterables.getOnlyElement(childFragments); for (Fragment fragment : childFragments) {
Pair<Integer, Object>[] eventData = fragment.getEventData(); if (fragment instanceof AudioSharingDialogFragment) {
eventData = ((AudioSharingDialogFragment) fragment).getEventData();
break;
}
}
assertThat(eventData) assertThat(eventData)
.asList() .asList()
.containsExactly( .containsExactly(
@@ -570,6 +611,8 @@ public class AudioSharingSwitchBarControllerTest {
AudioSharingUtils.MetricKey.METRIC_KEY_CANDIDATE_DEVICE_COUNT AudioSharingUtils.MetricKey.METRIC_KEY_CANDIDATE_DEVICE_COUNT
.ordinal(), .ordinal(),
1)); 1));
childFragments.forEach(fragment -> ((DialogFragment) fragment).dismiss());
} }
@Test @Test
@@ -582,6 +625,8 @@ public class AudioSharingSwitchBarControllerTest {
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);
shadowOf(Looper.getMainLooper()).idle();
verify(mBroadcast).startPrivateBroadcast(); verify(mBroadcast).startPrivateBroadcast();
mController.mBroadcastCallback.onPlaybackStarted(0, 0); mController.mBroadcastCallback.onPlaybackStarted(0, 0);
shadowOf(Looper.getMainLooper()).idle(); shadowOf(Looper.getMainLooper()).idle();
@@ -597,6 +642,17 @@ public class AudioSharingSwitchBarControllerTest {
verify(mAssistant).addSource(mDevice1, mMetadata, /* isGroupOp= */ false); verify(mAssistant).addSource(mDevice1, mMetadata, /* isGroupOp= */ false);
assertThat(dialog.isShowing()).isFalse(); assertThat(dialog.isShowing()).isFalse();
// Loading state dialog shows sharing state for the user chosen sink.
List<Fragment> childFragments = mParentFragment.getChildFragmentManager().getFragments();
assertThat(childFragments).comparingElementsUsing(CLAZZNAME_EQUALS).containsExactly(
AudioSharingLoadingStateDialogFragment.class.getName());
AudioSharingLoadingStateDialogFragment loadingFragment =
(AudioSharingLoadingStateDialogFragment) Iterables.getOnlyElement(childFragments);
// TODO: use string res once finalized
String expectedMessage = "Sharing with " + TEST_DEVICE_NAME1 + "...";
checkLoadingStateDialogMessage(loadingFragment, expectedMessage);
childFragments.forEach(fragment -> ((DialogFragment) fragment).dismiss());
} }
@Test @Test
@@ -609,6 +665,8 @@ public class AudioSharingSwitchBarControllerTest {
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);
shadowOf(Looper.getMainLooper()).idle();
verify(mBroadcast).startPrivateBroadcast(); verify(mBroadcast).startPrivateBroadcast();
mController.mBroadcastCallback.onPlaybackStarted(0, 0); mController.mBroadcastCallback.onPlaybackStarted(0, 0);
shadowOf(Looper.getMainLooper()).idle(); shadowOf(Looper.getMainLooper()).idle();
@@ -624,10 +682,21 @@ public class AudioSharingSwitchBarControllerTest {
verify(mAssistant, never()).addSource(mDevice1, mMetadata, /* isGroupOp= */ false); verify(mAssistant, never()).addSource(mDevice1, mMetadata, /* isGroupOp= */ false);
assertThat(dialog.isShowing()).isFalse(); assertThat(dialog.isShowing()).isFalse();
// Loading state dialog shows sharing state for the auto add active sink.
List<Fragment> childFragments = mParentFragment.getChildFragmentManager().getFragments();
assertThat(childFragments).comparingElementsUsing(CLAZZNAME_EQUALS).containsExactly(
AudioSharingLoadingStateDialogFragment.class.getName());
AudioSharingLoadingStateDialogFragment loadingFragment =
(AudioSharingLoadingStateDialogFragment) Iterables.getOnlyElement(childFragments);
// TODO: use string res once finalized
String expectedMessage = "Sharing with " + TEST_DEVICE_NAME2 + "...";
checkLoadingStateDialogMessage(loadingFragment, expectedMessage);
childFragments.forEach(fragment -> ((DialogFragment) fragment).dismiss());
} }
@Test @Test
public void testBluetoothLeBroadcastCallbacks_updateSwitch() { public void testBroadcastCallbacks_updateSwitch() {
mOnAudioSharingStateChanged = false; mOnAudioSharingStateChanged = false;
mSwitchBar.setChecked(false); mSwitchBar.setChecked(false);
when(mBroadcast.isEnabled(any())).thenReturn(false); when(mBroadcast.isEnabled(any())).thenReturn(false);
@@ -673,7 +742,7 @@ public class AudioSharingSwitchBarControllerTest {
} }
@Test @Test
public void testBluetoothLeBroadcastCallbacks_doNothing() { public void testBroadcastCallbacks_doNothing() {
mController.mBroadcastCallback.onBroadcastMetadataChanged(/* reason= */ 1, mMetadata); mController.mBroadcastCallback.onBroadcastMetadataChanged(/* reason= */ 1, mMetadata);
mController.mBroadcastCallback.onBroadcastUpdated(/* reason= */ 1, /* broadcastId= */ 1); mController.mBroadcastCallback.onBroadcastUpdated(/* reason= */ 1, /* broadcastId= */ 1);
mController.mBroadcastCallback.onPlaybackStarted(/* reason= */ 1, /* broadcastId= */ 1); mController.mBroadcastCallback.onPlaybackStarted(/* reason= */ 1, /* broadcastId= */ 1);
@@ -685,7 +754,7 @@ public class AudioSharingSwitchBarControllerTest {
} }
@Test @Test
public void testBluetoothLeBroadcastAssistantCallbacks_logAction() { public void testAssistantCallbacks_onSourceAddFailed_logAction() {
mController.mBroadcastAssistantCallback.onSourceAddFailed( mController.mBroadcastAssistantCallback.onSourceAddFailed(
mDevice1, mMetadata, /* reason= */ 1); mDevice1, mMetadata, /* reason= */ 1);
verify(mFeatureFactory.metricsFeatureProvider) verify(mFeatureFactory.metricsFeatureProvider)
@@ -696,7 +765,24 @@ public class AudioSharingSwitchBarControllerTest {
} }
@Test @Test
public void testBluetoothLeBroadcastAssistantCallbacks_doNothing() { public void testAssistantCallbacks_onReceiveStateChanged_dismissLoadingDialog() {
AudioSharingLoadingStateDialogFragment.show(mParentFragment, TEST_DEVICE_NAME1);
shadowOf(Looper.getMainLooper()).idle();
List<Fragment> childFragments = mParentFragment.getChildFragmentManager().getFragments();
assertThat(childFragments).comparingElementsUsing(CLAZZNAME_EQUALS).containsExactly(
AudioSharingLoadingStateDialogFragment.class.getName());
BluetoothLeBroadcastReceiveState state = mock(BluetoothLeBroadcastReceiveState.class);
when(state.getBisSyncState()).thenReturn(ImmutableList.of(1L));
mController.mBroadcastAssistantCallback.onReceiveStateChanged(mDevice1, /* sourceId= */ 1,
state);
shadowOf(Looper.getMainLooper()).idle();
childFragments = mParentFragment.getChildFragmentManager().getFragments();
assertThat(childFragments).isEmpty();
}
@Test
public void testAssistantCallbacks_doNothing() {
BluetoothLeBroadcastReceiveState state = mock(BluetoothLeBroadcastReceiveState.class); BluetoothLeBroadcastReceiveState state = mock(BluetoothLeBroadcastReceiveState.class);
// Do nothing // Do nothing
@@ -784,7 +870,7 @@ public class AudioSharingSwitchBarControllerTest {
@Test @Test
public void handleStartAudioSharingFromIntent_flagOff_doNothing() { public void handleStartAudioSharingFromIntent_flagOff_doNothing() {
mSetFlagsRule.disableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING); mSetFlagsRule.disableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
setUpStartSharingIntent(); var unused = setUpFragmentWithStartSharingIntent();
mController.onStart(mLifecycleOwner); mController.onStart(mLifecycleOwner);
shadowOf(Looper.getMainLooper()).idle(); shadowOf(Looper.getMainLooper()).idle();
@@ -795,7 +881,7 @@ public class AudioSharingSwitchBarControllerTest {
public void handleStartAudioSharingFromIntent_profileNotReady_doNothing() { public void handleStartAudioSharingFromIntent_profileNotReady_doNothing() {
mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING); mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
when(mAssistant.isProfileReady()).thenReturn(false); when(mAssistant.isProfileReady()).thenReturn(false);
setUpStartSharingIntent(); var unused = setUpFragmentWithStartSharingIntent();
mController.onServiceConnected(); mController.onServiceConnected();
shadowOf(Looper.getMainLooper()).idle(); shadowOf(Looper.getMainLooper()).idle();
@@ -817,13 +903,16 @@ public class AudioSharingSwitchBarControllerTest {
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(mBroadcast.getLatestBluetoothLeBroadcastMetadata()).thenReturn(mMetadata); when(mBroadcast.getLatestBluetoothLeBroadcastMetadata()).thenReturn(mMetadata);
setUpStartSharingIntent(); Fragment parentFragment = setUpFragmentWithStartSharingIntent();
mController.onServiceConnected(); mController.onServiceConnected();
shadowOf(Looper.getMainLooper()).idle(); shadowOf(Looper.getMainLooper()).idle();
verify(mSwitchBar).setChecked(true); verify(mSwitchBar).setChecked(true);
doNothing().when(mBroadcast).startPrivateBroadcast(); doNothing().when(mBroadcast).startPrivateBroadcast();
mController.onCheckedChanged(mBtnView, /* isChecked= */ true); mController.onCheckedChanged(mBtnView, /* isChecked= */ true);
shadowOf(Looper.getMainLooper()).idle();
verify(mBroadcast).startPrivateBroadcast();
mController.mBroadcastCallback.onPlaybackStarted(0, 0); mController.mBroadcastCallback.onPlaybackStarted(0, 0);
shadowOf(Looper.getMainLooper()).idle(); shadowOf(Looper.getMainLooper()).idle();
@@ -831,11 +920,21 @@ public class AudioSharingSwitchBarControllerTest {
.action(any(Context.class), eq(SettingsEnums.ACTION_AUTO_JOIN_AUDIO_SHARING)); .action(any(Context.class), eq(SettingsEnums.ACTION_AUTO_JOIN_AUDIO_SHARING));
verify(mAssistant).addSource(mDevice1, mMetadata, /* isGroupOp= */ false); verify(mAssistant).addSource(mDevice1, mMetadata, /* isGroupOp= */ false);
verify(mAssistant).addSource(mDevice2, mMetadata, /* isGroupOp= */ false); verify(mAssistant).addSource(mDevice2, mMetadata, /* isGroupOp= */ false);
List<Fragment> childFragments = mParentFragment.getChildFragmentManager().getFragments(); List<Fragment> childFragments = parentFragment.getChildFragmentManager().getFragments();
assertThat(childFragments).isEmpty(); // Skip audio sharing dialog.
assertThat(childFragments).comparingElementsUsing(CLAZZNAME_EQUALS).containsExactly(
AudioSharingLoadingStateDialogFragment.class.getName());
// The loading state dialog shows sharing state for the auto add second sink.
AudioSharingLoadingStateDialogFragment loadingFragment =
(AudioSharingLoadingStateDialogFragment) Iterables.getOnlyElement(childFragments);
// TODO: use string res once finalized
String expectedMessage = "Sharing with " + TEST_DEVICE_NAME1 + "...";
checkLoadingStateDialogMessage(loadingFragment, expectedMessage);
childFragments.forEach(fragment -> ((DialogFragment) fragment).dismiss());
} }
private void setUpStartSharingIntent() { private Fragment setUpFragmentWithStartSharingIntent() {
Bundle args = new Bundle(); Bundle args = new Bundle();
args.putBoolean(EXTRA_START_LE_AUDIO_SHARING, true); args.putBoolean(EXTRA_START_LE_AUDIO_SHARING, true);
Intent intent = new Intent(); Intent intent = new Intent();
@@ -849,5 +948,15 @@ public class AudioSharingSwitchBarControllerTest {
.get(); .get();
shadowOf(Looper.getMainLooper()).idle(); shadowOf(Looper.getMainLooper()).idle();
mController.init(fragment); mController.init(fragment);
return fragment;
}
private void checkLoadingStateDialogMessage(
@NonNull AudioSharingLoadingStateDialogFragment fragment,
@NonNull String expectedMessage) {
TextView loadingMessage = fragment.getDialog() == null ? null
: fragment.getDialog().findViewById(R.id.message);
assertThat(loadingMessage).isNotNull();
assertThat(loadingMessage.getText().toString()).isEqualTo(expectedMessage);
} }
} }