[Audiosharing] Refine share then pair flow

Currently when there is one active LEA headset and users toggle on the audio sharing, a dialog will pop up to ask users to pair new headset and share audio with it. After user click pair new device button on the dialog:

1. Route users to pair new device page.
2. If users pair an LEA headset, finish the pair new device page and
   auto add source to the headset with loading indicators on audio
   sharing page.
3. If users pair a classic headset, wait for timeout, pop up dialog
   saying the paired headset is not compatible for audio sharing and
   finish the pair new device page.

Test: atest
Flag: com.android.settingslib.flags.enable_le_audio_sharing
Bug: 331892035
Change-Id: Ifb9579db0ef57d3a379cb5d17c66a604d1396bb4
This commit is contained in:
Yiyi Shen
2024-08-15 16:50:52 +08:00
parent 958639b79c
commit 800f81c832
11 changed files with 672 additions and 75 deletions

View File

@@ -16,52 +16,80 @@
package com.android.settings.bluetooth;
import static com.android.settingslib.bluetooth.LocalBluetoothLeBroadcast.EXTRA_BT_DEVICE_TO_AUTO_ADD_SOURCE;
import static com.android.settingslib.bluetooth.LocalBluetoothLeBroadcast.EXTRA_PAIR_AND_JOIN_SHARING;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static org.robolectric.Shadows.shadowOf;
import android.app.Activity;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothProfile;
import android.bluetooth.BluetoothStatusCodes;
import android.content.Context;
import android.content.Intent;
import android.content.res.Resources;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.os.Looper;
import android.platform.test.flag.junit.SetFlagsRule;
import android.util.Pair;
import android.widget.TextView;
import androidx.appcompat.app.AlertDialog;
import androidx.fragment.app.FragmentActivity;
import androidx.fragment.app.FragmentManager;
import androidx.test.core.app.ApplicationProvider;
import com.android.settings.R;
import com.android.settings.SettingsActivity;
import com.android.settings.testutils.shadow.ShadowAlertDialogCompat;
import com.android.settings.testutils.shadow.ShadowBluetoothAdapter;
import com.android.settingslib.bluetooth.CachedBluetoothDevice;
import com.android.settingslib.bluetooth.LocalBluetoothManager;
import com.android.settingslib.flags.Flags;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Answers;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
import org.robolectric.Robolectric;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.annotation.Config;
import org.robolectric.shadow.api.Shadow;
import java.util.concurrent.Executor;
/** Tests for {@link BluetoothDevicePairingDetailBase}. */
@RunWith(RobolectricTestRunner.class)
@Config(shadows = {
ShadowBluetoothAdapter.class,
ShadowAlertDialogCompat.class,
com.android.settings.testutils.shadow.ShadowFragment.class,
})
public class BluetoothDevicePairingDetailBaseTest {
@Rule
public final MockitoRule mockito = MockitoJUnit.rule();
public final MockitoRule mMockitoRule = MockitoJUnit.rule();
@Rule
public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
public static final String KEY_DEVICE_LIST_GROUP = "test_key";
@@ -86,8 +114,12 @@ public class BluetoothDevicePairingDetailBaseTest {
@Before
public void setUp() {
mAvailableDevicesCategory = spy(new BluetoothProgressCategory(mContext));
mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
mBluetoothAdapter = spy(BluetoothAdapter.getDefaultAdapter());
mShadowBluetoothAdapter = Shadow.extract(BluetoothAdapter.getDefaultAdapter());
mShadowBluetoothAdapter.setIsLeAudioBroadcastSourceSupported(
BluetoothStatusCodes.FEATURE_SUPPORTED);
mShadowBluetoothAdapter.setIsLeAudioBroadcastAssistantSupported(
BluetoothStatusCodes.FEATURE_SUPPORTED);
when(mCachedBluetoothDevice.getAddress()).thenReturn(TEST_DEVICE_ADDRESS);
final Pair<Drawable, String> pairs = new Pair<>(mDrawable, "fake_device");
when(mCachedBluetoothDevice.getDrawableWithDescription()).thenReturn(pairs);
@@ -155,8 +187,88 @@ public class BluetoothDevicePairingDetailBaseTest {
verify(mFragment).showBluetoothTurnedOnToast();
}
@Test
public void onDeviceBondStateChanged_bonded_pairAndJoinSharingDisabled_finish() {
mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
when(mCachedBluetoothDevice.getDevice()).thenReturn(mBluetoothDevice);
mFragment.mSelectedList.add(mBluetoothDevice);
setUpFragmentWithPairAndJoinSharingIntent(false);
mFragment.onDeviceBondStateChanged(mCachedBluetoothDevice, BluetoothDevice.BOND_BONDED);
verify(mFragment).finish();
}
@Test
public void onDeviceBondStateChanged_bonded_pairAndJoinSharingEnabled_handle() {
mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
when(mCachedBluetoothDevice.getDevice()).thenReturn(mBluetoothDevice);
mFragment.mSelectedList.add(mBluetoothDevice);
setUpFragmentWithPairAndJoinSharingIntent(true);
mFragment.onDeviceBondStateChanged(mCachedBluetoothDevice, BluetoothDevice.BOND_BONDED);
shadowOf(Looper.getMainLooper()).idle();
AlertDialog dialog = ShadowAlertDialogCompat.getLatestAlertDialog();
assertThat(dialog).isNotNull();
TextView message = dialog.findViewById(R.id.message);
assertThat(message).isNotNull();
// TODO: use stringr res once finalized
assertThat(message.getText().toString()).isEqualTo(
"Connecting to " + TEST_DEVICE_ADDRESS + "...");
verify(mFragment, never()).finish();
}
@Test
public void onDeviceBondStateChanged_bonding_pairAndJoinSharingDisabled_doNothing() {
mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
when(mCachedBluetoothDevice.getDevice()).thenReturn(mBluetoothDevice);
mFragment.mSelectedList.add(mBluetoothDevice);
setUpFragmentWithPairAndJoinSharingIntent(false);
mFragment.onDeviceBondStateChanged(mCachedBluetoothDevice, BluetoothDevice.BOND_BONDING);
verify(mBluetoothAdapter, never()).addOnMetadataChangedListener(any(BluetoothDevice.class),
any(Executor.class), any(BluetoothAdapter.OnMetadataChangedListener.class));
}
@Test
public void onDeviceBondStateChanged_bonding_pairAndJoinSharingEnabled_addListener() {
mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
when(mCachedBluetoothDevice.getDevice()).thenReturn(mBluetoothDevice);
mFragment.mSelectedList.add(mBluetoothDevice);
setUpFragmentWithPairAndJoinSharingIntent(true);
mFragment.onDeviceBondStateChanged(mCachedBluetoothDevice, BluetoothDevice.BOND_BONDING);
verify(mBluetoothAdapter).addOnMetadataChangedListener(eq(mBluetoothDevice),
any(Executor.class),
any(BluetoothAdapter.OnMetadataChangedListener.class));
}
@Test
public void onDeviceBondStateChanged_unbonded_pairAndJoinSharingDisabled_doNothing() {
mSetFlagsRule.disableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
when(mCachedBluetoothDevice.getDevice()).thenReturn(mBluetoothDevice);
mFragment.mSelectedList.add(mBluetoothDevice);
mFragment.onDeviceBondStateChanged(mCachedBluetoothDevice, BluetoothDevice.BOND_NONE);
verify(mBluetoothAdapter, never()).removeOnMetadataChangedListener(
any(BluetoothDevice.class), any(BluetoothAdapter.OnMetadataChangedListener.class));
}
@Test
public void onDeviceBondStateChanged_unbonded_pairAndJoinSharingEnabled_removeListener() {
mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
when(mCachedBluetoothDevice.getDevice()).thenReturn(mBluetoothDevice);
mFragment.mSelectedList.add(mBluetoothDevice);
setUpFragmentWithPairAndJoinSharingIntent(true);
mFragment.onDeviceBondStateChanged(mCachedBluetoothDevice, BluetoothDevice.BOND_BONDING);
mFragment.onDeviceBondStateChanged(mCachedBluetoothDevice, BluetoothDevice.BOND_NONE);
verify(mBluetoothAdapter).removeOnMetadataChangedListener(eq(mBluetoothDevice),
any(BluetoothAdapter.OnMetadataChangedListener.class));
}
@Test
public void onProfileConnectionStateChanged_deviceInSelectedListAndConnected_finish() {
mSetFlagsRule.disableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
final BluetoothDevice device = mBluetoothAdapter.getRemoteDevice(TEST_DEVICE_ADDRESS_B);
mFragment.mSelectedList.add(mBluetoothDevice);
mFragment.mSelectedList.add(device);
@@ -165,13 +277,43 @@ public class BluetoothDevicePairingDetailBaseTest {
when(mCachedBluetoothDevice.getDevice()).thenReturn(device);
mFragment.onProfileConnectionStateChanged(mCachedBluetoothDevice,
BluetoothProfile.A2DP, BluetoothAdapter.STATE_CONNECTED);
BluetoothAdapter.STATE_CONNECTED, BluetoothProfile.A2DP);
verify(mFragment).finish();
}
@Test
public void
onProfileConnectionStateChanged_deviceInSelectedListAndConnected_pairAndJoinSharing() {
mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
when(mCachedBluetoothDevice.getDevice()).thenReturn(mBluetoothDevice);
mFragment.mSelectedList.add(mBluetoothDevice);
setUpFragmentWithPairAndJoinSharingIntent(true);
mFragment.onDeviceBondStateChanged(mCachedBluetoothDevice, BluetoothDevice.BOND_BONDED);
shadowOf(Looper.getMainLooper()).idle();
when(mCachedBluetoothDevice.isConnected()).thenReturn(true);
mFragment.onProfileConnectionStateChanged(mCachedBluetoothDevice,
BluetoothAdapter.STATE_CONNECTED, BluetoothProfile.LE_AUDIO_BROADCAST_ASSISTANT);
shadowOf(Looper.getMainLooper()).idle();
ArgumentCaptor<Intent> captor = ArgumentCaptor.forClass(Intent.class);
verify(mFragment.getActivity()).setResult(eq(Activity.RESULT_OK), captor.capture());
Intent intent = captor.getValue();
BluetoothDevice btDevice =
intent != null
? intent.getParcelableExtra(EXTRA_BT_DEVICE_TO_AUTO_ADD_SOURCE,
BluetoothDevice.class)
: null;
assertThat(btDevice).isNotNull();
assertThat(btDevice).isEqualTo(mBluetoothDevice);
verify(mFragment).finish();
}
@Test
public void onProfileConnectionStateChanged_deviceNotInSelectedList_doNothing() {
mSetFlagsRule.disableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
final BluetoothDevice device = mBluetoothAdapter.getRemoteDevice(TEST_DEVICE_ADDRESS_B);
mFragment.mSelectedList.add(device);
@@ -179,13 +321,14 @@ public class BluetoothDevicePairingDetailBaseTest {
when(mCachedBluetoothDevice.getDevice()).thenReturn(mBluetoothDevice);
mFragment.onProfileConnectionStateChanged(mCachedBluetoothDevice,
BluetoothProfile.A2DP, BluetoothAdapter.STATE_CONNECTED);
BluetoothAdapter.STATE_CONNECTED, BluetoothProfile.A2DP);
// not crash
}
@Test
public void onProfileConnectionStateChanged_deviceDisconnected_doNothing() {
mSetFlagsRule.disableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
final BluetoothDevice device = mBluetoothAdapter.getRemoteDevice(TEST_DEVICE_ADDRESS_B);
mFragment.mSelectedList.add(mBluetoothDevice);
mFragment.mSelectedList.add(device);
@@ -194,13 +337,14 @@ public class BluetoothDevicePairingDetailBaseTest {
when(mCachedBluetoothDevice.getDevice()).thenReturn(device);
mFragment.onProfileConnectionStateChanged(mCachedBluetoothDevice,
BluetoothProfile.A2DP, BluetoothAdapter.STATE_DISCONNECTED);
BluetoothAdapter.STATE_DISCONNECTED, BluetoothProfile.A2DP);
// not crash
}
@Test
public void onProfileConnectionStateChanged_deviceInPreferenceMapAndConnected_removed() {
mSetFlagsRule.disableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
final BluetoothDevicePreference preference =
new BluetoothDevicePreference(mContext, mCachedBluetoothDevice,
true, BluetoothDevicePreference.SortType.TYPE_FIFO);
@@ -211,13 +355,14 @@ public class BluetoothDevicePairingDetailBaseTest {
when(mCachedBluetoothDevice.getDevice()).thenReturn(device);
mFragment.onProfileConnectionStateChanged(mCachedBluetoothDevice,
BluetoothProfile.A2DP, BluetoothAdapter.STATE_CONNECTED);
BluetoothAdapter.STATE_CONNECTED, BluetoothProfile.A2DP);
assertThat(mFragment.getDevicePreferenceMap().size()).isEqualTo(0);
}
@Test
public void onProfileConnectionStateChanged_deviceNotInPreferenceMap_doNothing() {
mSetFlagsRule.disableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
final CachedBluetoothDevice cachedDevice = mock(CachedBluetoothDevice.class);
final BluetoothDevicePreference preference =
new BluetoothDevicePreference(mContext, mCachedBluetoothDevice,
@@ -233,12 +378,26 @@ public class BluetoothDevicePairingDetailBaseTest {
when(cachedDevice.getAddress()).thenReturn(TEST_DEVICE_ADDRESS_B);
when(cachedDevice.getIdentityAddress()).thenReturn(TEST_DEVICE_ADDRESS_B);
mFragment.onProfileConnectionStateChanged(cachedDevice, BluetoothProfile.A2DP,
BluetoothAdapter.STATE_CONNECTED);
mFragment.onProfileConnectionStateChanged(cachedDevice, BluetoothAdapter.STATE_CONNECTED,
BluetoothProfile.A2DP);
// not crash
}
private void setUpFragmentWithPairAndJoinSharingIntent(boolean enablePairAndJoinSharing) {
Bundle args = new Bundle();
args.putBoolean(EXTRA_PAIR_AND_JOIN_SHARING, enablePairAndJoinSharing);
Intent intent = new Intent();
intent.putExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT_ARGUMENTS, args);
FragmentActivity activity = spy(Robolectric.setupActivity(FragmentActivity.class));
doReturn(intent).when(activity).getIntent();
doReturn(activity).when(mFragment).getActivity();
FragmentManager fragmentManager = mock(FragmentManager.class);
doReturn(fragmentManager).when(mFragment).getFragmentManager();
mFragment.mShouldTriggerAudioSharingShareThenPairFlow =
mFragment.shouldTriggerAudioSharingShareThenPairFlow();
}
private static class TestBluetoothDevicePairingDetailBase extends
BluetoothDevicePairingDetailBase {