From 4b85389124a528fa4ecf122ae9b4a44fc15c26d1 Mon Sep 17 00:00:00 2001 From: chelseahao Date: Wed, 19 Jun 2024 11:57:39 +0800 Subject: [PATCH] [Audiosharing] Increase test coverage for audio stream states. 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: I91d9a45abd4c9659c9d0ddeca5f5aaefed36f820 --- .../AddSourceWaitForResponseState.java | 3 +- .../audiostreams/AudioStreamStateHandler.java | 7 +- .../audiostreams/AudioStreamsHelper.java | 6 +- .../audiostreams/SourceAddedState.java | 6 +- .../audiostreams/WaitForSyncState.java | 6 +- .../AddSourceBadCodeStateTest.java | 39 ++++++- .../AddSourceFailedStateTest.java | 39 ++++++- .../AddSourceWaitForResponseStateTest.java | 54 ++++++++- .../audiostreams/SourceAddedStateTest.java | 103 +++++++++++++++++- .../audiostreams/SyncedStateTest.java | 51 ++++++++- .../audiostreams/WaitForSyncStateTest.java | 61 ++++++++++- 11 files changed, 351 insertions(+), 24 deletions(-) diff --git a/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AddSourceWaitForResponseState.java b/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AddSourceWaitForResponseState.java index 24a28dd0602..7be01a20235 100644 --- a/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AddSourceWaitForResponseState.java +++ b/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AddSourceWaitForResponseState.java @@ -36,7 +36,8 @@ class AddSourceWaitForResponseState extends AudioStreamStateHandler { @Nullable private static AddSourceWaitForResponseState sInstance = null; - private AddSourceWaitForResponseState() {} + @VisibleForTesting + AddSourceWaitForResponseState() {} static AddSourceWaitForResponseState getInstance() { if (sInstance == null) { diff --git a/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamStateHandler.java b/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamStateHandler.java index b0c5b6baebf..4bb84751b36 100644 --- a/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamStateHandler.java +++ b/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamStateHandler.java @@ -35,10 +35,10 @@ class AudioStreamStateHandler { private static final boolean DEBUG = BluetoothUtils.D; @VisibleForTesting static final int EMPTY_STRING_RES = 0; - final AudioStreamsRepository mAudioStreamsRepository = AudioStreamsRepository.getInstance(); final Handler mHandler = new Handler(Looper.getMainLooper()); final MetricsFeatureProvider mMetricsFeatureProvider = FeatureFactory.getFeatureFactory().getMetricsFeatureProvider(); + AudioStreamsRepository mAudioStreamsRepository = AudioStreamsRepository.getInstance(); AudioStreamStateHandler() {} @@ -112,4 +112,9 @@ class AudioStreamStateHandler { AudioStreamsProgressCategoryController.AudioStreamState getStateEnum() { return AudioStreamsProgressCategoryController.AudioStreamState.UNKNOWN; } + + @VisibleForTesting + void setAudioStreamsRepositoryForTesting(AudioStreamsRepository repository) { + mAudioStreamsRepository = repository; + } } diff --git a/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsHelper.java b/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsHelper.java index 775186a859e..6e335a0971c 100644 --- a/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsHelper.java +++ b/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsHelper.java @@ -71,7 +71,8 @@ public class AudioStreamsHelper { * * @param source The LE broadcast metadata representing the audio source. */ - void addSource(BluetoothLeBroadcastMetadata source) { + @VisibleForTesting + public void addSource(BluetoothLeBroadcastMetadata source) { if (mLeBroadcastAssistant == null) { Log.w(TAG, "addSource(): LeBroadcastAssistant is null!"); return; @@ -97,7 +98,8 @@ public class AudioStreamsHelper { } /** Removes sources from LE broadcasts associated for all active sinks based on broadcast Id. */ - void removeSource(int broadcastId) { + @VisibleForTesting + public void removeSource(int broadcastId) { if (mLeBroadcastAssistant == null) { Log.w(TAG, "removeSource(): LeBroadcastAssistant is null!"); return; diff --git a/src/com/android/settings/connecteddevice/audiosharing/audiostreams/SourceAddedState.java b/src/com/android/settings/connecteddevice/audiosharing/audiostreams/SourceAddedState.java index ee84429663a..4f36db9363c 100644 --- a/src/com/android/settings/connecteddevice/audiosharing/audiostreams/SourceAddedState.java +++ b/src/com/android/settings/connecteddevice/audiosharing/audiostreams/SourceAddedState.java @@ -32,7 +32,8 @@ class SourceAddedState extends AudioStreamStateHandler { @Nullable private static SourceAddedState sInstance = null; - private SourceAddedState() {} + @VisibleForTesting + SourceAddedState() {} static SourceAddedState getInstance() { if (sInstance == null) { @@ -80,8 +81,7 @@ class SourceAddedState extends AudioStreamStateHandler { AudioStreamDetailsFragment.BROADCAST_ID_ARG, p.getAudioStreamBroadcastId()); new SubSettingLauncher(p.getContext()) - .setTitleText( - p.getContext().getString(R.string.audio_streams_detail_page_title)) + .setTitleRes(R.string.audio_streams_detail_page_title) .setDestination(AudioStreamDetailsFragment.class.getName()) .setSourceMetricsCategory( controller.getFragment() == null diff --git a/src/com/android/settings/connecteddevice/audiosharing/audiostreams/WaitForSyncState.java b/src/com/android/settings/connecteddevice/audiosharing/audiostreams/WaitForSyncState.java index 55f61fdd0e2..9689b263a47 100644 --- a/src/com/android/settings/connecteddevice/audiosharing/audiostreams/WaitForSyncState.java +++ b/src/com/android/settings/connecteddevice/audiosharing/audiostreams/WaitForSyncState.java @@ -39,7 +39,8 @@ class WaitForSyncState extends AudioStreamStateHandler { @Nullable private static WaitForSyncState sInstance = null; - private WaitForSyncState() {} + @VisibleForTesting + WaitForSyncState() {} static WaitForSyncState getInstance() { if (sInstance == null) { @@ -114,7 +115,8 @@ class WaitForSyncState extends AudioStreamStateHandler { SettingsEnums.DIALOG_AUDIO_STREAM_MAIN_WAIT_FOR_SYNC_TIMEOUT); } - private void launchQrCodeScanFragment(Context context, Fragment fragment) { + @VisibleForTesting + void launchQrCodeScanFragment(Context context, Fragment fragment) { new SubSettingLauncher(context) .setTitleRes(R.string.audio_streams_main_page_scan_qr_code_title) .setDestination(AudioStreamsQrCodeScanFragment.class.getName()) diff --git a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AddSourceBadCodeStateTest.java b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AddSourceBadCodeStateTest.java index 2fddff52358..aba300e08bc 100644 --- a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AddSourceBadCodeStateTest.java +++ b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AddSourceBadCodeStateTest.java @@ -20,18 +20,40 @@ import static com.android.settings.connecteddevice.audiosharing.audiostreams.Add import static com.google.common.truth.Truth.assertThat; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.app.settings.SettingsEnums; +import android.content.Context; + +import androidx.test.core.app.ApplicationProvider; + +import com.android.settings.testutils.FakeFeatureFactory; + import org.junit.Before; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; import org.robolectric.RobolectricTestRunner; @RunWith(RobolectricTestRunner.class) public class AddSourceBadCodeStateTest { + @Rule public final MockitoRule mMockitoRule = MockitoJUnit.rule(); + private final Context mContext = ApplicationProvider.getApplicationContext(); + @Mock private AudioStreamPreference mPreference; + @Mock private AudioStreamsProgressCategoryController mController; + @Mock private AudioStreamsHelper mHelper; + private FakeFeatureFactory mFeatureFactory; private AddSourceBadCodeState mInstance; @Before public void setUp() { - mInstance = AddSourceBadCodeState.getInstance(); + mFeatureFactory = FakeFeatureFactory.setupForTest(); + mInstance = new AddSourceBadCodeState(); } @Test @@ -55,4 +77,19 @@ public class AddSourceBadCodeStateTest { AudioStreamsProgressCategoryController.AudioStreamState .ADD_SOURCE_BAD_CODE); } + + @Test + public void testPerformAction() { + when(mPreference.getContext()).thenReturn(mContext); + when(mPreference.getSourceOriginForLogging()) + .thenReturn(SourceOriginForLogging.QR_CODE_SCAN_SETTINGS); + + mInstance.performAction(mPreference, mController, mHelper); + + verify(mFeatureFactory.metricsFeatureProvider) + .action( + eq(mContext), + eq(SettingsEnums.ACTION_AUDIO_STREAM_JOIN_FAILED_BAD_CODE), + eq(SourceOriginForLogging.QR_CODE_SCAN_SETTINGS.ordinal())); + } } diff --git a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AddSourceFailedStateTest.java b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AddSourceFailedStateTest.java index d8b1fcf9401..1bc9f9148f5 100644 --- a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AddSourceFailedStateTest.java +++ b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AddSourceFailedStateTest.java @@ -20,18 +20,40 @@ import static com.android.settings.connecteddevice.audiosharing.audiostreams.Add import static com.google.common.truth.Truth.assertThat; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.app.settings.SettingsEnums; +import android.content.Context; + +import androidx.test.core.app.ApplicationProvider; + +import com.android.settings.testutils.FakeFeatureFactory; + import org.junit.Before; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; import org.robolectric.RobolectricTestRunner; @RunWith(RobolectricTestRunner.class) public class AddSourceFailedStateTest { + @Rule public final MockitoRule mMockitoRule = MockitoJUnit.rule(); + private final Context mContext = ApplicationProvider.getApplicationContext(); + @Mock private AudioStreamPreference mPreference; + @Mock private AudioStreamsProgressCategoryController mController; + @Mock private AudioStreamsHelper mHelper; + private FakeFeatureFactory mFeatureFactory; private AddSourceFailedState mInstance; @Before public void setUp() { - mInstance = AddSourceFailedState.getInstance(); + mFeatureFactory = FakeFeatureFactory.setupForTest(); + mInstance = new AddSourceFailedState(); } @Test @@ -54,4 +76,19 @@ public class AddSourceFailedStateTest { .isEqualTo( AudioStreamsProgressCategoryController.AudioStreamState.ADD_SOURCE_FAILED); } + + @Test + public void testPerformAction() { + when(mPreference.getContext()).thenReturn(mContext); + when(mPreference.getSourceOriginForLogging()) + .thenReturn(SourceOriginForLogging.QR_CODE_SCAN_SETTINGS); + + mInstance.performAction(mPreference, mController, mHelper); + + verify(mFeatureFactory.metricsFeatureProvider) + .action( + eq(mContext), + eq(SettingsEnums.ACTION_AUDIO_STREAM_JOIN_FAILED_OTHER), + eq(SourceOriginForLogging.QR_CODE_SCAN_SETTINGS.ordinal())); + } } diff --git a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AddSourceWaitForResponseStateTest.java b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AddSourceWaitForResponseStateTest.java index 6e5342bb3fe..950ad38a64a 100644 --- a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AddSourceWaitForResponseStateTest.java +++ b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AddSourceWaitForResponseStateTest.java @@ -22,11 +22,21 @@ 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.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 android.app.settings.SettingsEnums; import android.bluetooth.BluetoothLeBroadcastMetadata; +import android.content.Context; + +import androidx.test.core.app.ApplicationProvider; + +import com.android.settings.R; +import com.android.settings.testutils.FakeFeatureFactory; import org.junit.Before; import org.junit.Rule; @@ -36,23 +46,36 @@ 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.ShadowLooper; import java.util.concurrent.TimeUnit; @RunWith(RobolectricTestRunner.class) +@Config( + shadows = { + ShadowAlertDialog.class, + }) public class AddSourceWaitForResponseStateTest { - private static final int BROADCAST_ID = 1; @Rule public final MockitoRule mMockitoRule = MockitoJUnit.rule(); + private static final int BROADCAST_ID = 1; + private final Context mContext = spy(ApplicationProvider.getApplicationContext()); @Mock private AudioStreamPreference mMockPreference; @Mock private AudioStreamsProgressCategoryController mMockController; @Mock private AudioStreamsHelper mMockHelper; @Mock private BluetoothLeBroadcastMetadata mMockMetadata; + @Mock private AudioStreamsRepository mMockRepository; + private FakeFeatureFactory mFeatureFactory; private AddSourceWaitForResponseState mInstance; @Before public void setUp() { - mInstance = AddSourceWaitForResponseState.getInstance(); + mFeatureFactory = FakeFeatureFactory.setupForTest(); + mInstance = new AddSourceWaitForResponseState(); + when(mMockPreference.getContext()).thenReturn(mContext); + when(mMockPreference.getSourceOriginForLogging()) + .thenReturn(SourceOriginForLogging.QR_CODE_SCAN_SETTINGS); } @Test @@ -93,11 +116,18 @@ public class AddSourceWaitForResponseStateTest { public void testPerformAction_metadataIsNotNull_addSource() { when(mMockPreference.getAudioStreamMetadata()).thenReturn(mMockMetadata); when(mMockPreference.getSourceOriginForLogging()) - .thenReturn(SourceOriginForLogging.UNKNOWN); + .thenReturn(SourceOriginForLogging.QR_CODE_SCAN_SETTINGS); + mInstance.setAudioStreamsRepositoryForTesting(mMockRepository); mInstance.performAction(mMockPreference, mMockController, mMockHelper); verify(mMockHelper).addSource(mMockMetadata); + verify(mFeatureFactory.metricsFeatureProvider) + .action( + eq(mContext), + eq(SettingsEnums.ACTION_AUDIO_STREAM_JOIN), + eq(SourceOriginForLogging.QR_CODE_SCAN_SETTINGS.ordinal())); + verify(mMockRepository).cacheMetadata(mMockMetadata); verify(mMockController, never()).handleSourceFailedToConnect(anyInt()); } @@ -108,12 +138,28 @@ public class AddSourceWaitForResponseStateTest { when(mMockPreference.getAudioStreamState()).thenReturn(mInstance.getStateEnum()); when(mMockPreference.getAudioStreamBroadcastId()).thenReturn(BROADCAST_ID); when(mMockPreference.getSourceOriginForLogging()) - .thenReturn(SourceOriginForLogging.UNKNOWN); + .thenReturn(SourceOriginForLogging.QR_CODE_SCAN_SETTINGS); + when(mMockController.getFragment()).thenReturn(mock(AudioStreamsDashboardFragment.class)); + mInstance.setAudioStreamsRepositoryForTesting(mMockRepository); mInstance.performAction(mMockPreference, mMockController, mMockHelper); ShadowLooper.idleMainLooper(ADD_SOURCE_WAIT_FOR_RESPONSE_TIMEOUT_MILLIS, TimeUnit.SECONDS); verify(mMockHelper).addSource(mMockMetadata); verify(mMockController).handleSourceFailedToConnect(BROADCAST_ID); + verify(mFeatureFactory.metricsFeatureProvider) + .action( + eq(mContext), + eq(SettingsEnums.ACTION_AUDIO_STREAM_JOIN), + eq(SourceOriginForLogging.QR_CODE_SCAN_SETTINGS.ordinal())); + verify(mMockRepository).cacheMetadata(mMockMetadata); + verify(mFeatureFactory.metricsFeatureProvider) + .action( + eq(mContext), + eq(SettingsEnums.ACTION_AUDIO_STREAM_JOIN_FAILED_TIMEOUT), + eq(SourceOriginForLogging.QR_CODE_SCAN_SETTINGS.ordinal())); + verify(mContext).getString(R.string.audio_streams_dialog_stream_is_not_available); + verify(mContext).getString(R.string.audio_streams_is_not_playing); + verify(mContext).getString(R.string.audio_streams_dialog_close); } } diff --git a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/SourceAddedStateTest.java b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/SourceAddedStateTest.java index 0f0bafe217a..082735a31fc 100644 --- a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/SourceAddedStateTest.java +++ b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/SourceAddedStateTest.java @@ -16,27 +16,71 @@ package com.android.settings.connecteddevice.audiosharing.audiostreams; +import static android.app.settings.SettingsEnums.AUDIO_STREAM_MAIN; + import static com.android.settings.connecteddevice.audiosharing.audiostreams.SourceAddedState.AUDIO_STREAM_SOURCE_ADDED_STATE_SUMMARY; import static com.google.common.truth.Truth.assertThat; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.app.settings.SettingsEnums; +import android.bluetooth.BluetoothLeBroadcastMetadata; +import android.content.Context; +import android.content.Intent; +import android.os.Bundle; + +import androidx.fragment.app.FragmentActivity; +import androidx.preference.Preference; +import androidx.test.core.app.ApplicationProvider; + +import com.android.settings.R; +import com.android.settings.SettingsActivity; +import com.android.settings.testutils.FakeFeatureFactory; +import com.android.settings.testutils.shadow.ShadowFragment; +import com.android.settingslib.core.instrumentation.MetricsFeatureProvider; + import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; import org.mockito.junit.MockitoJUnit; import org.mockito.junit.MockitoRule; import org.robolectric.RobolectricTestRunner; +import org.robolectric.annotation.Config; @RunWith(RobolectricTestRunner.class) +@Config( + shadows = { + ShadowFragment.class, + }) public class SourceAddedStateTest { - @Rule public final MockitoRule mMockitoRule = MockitoJUnit.rule(); + private static final int BROADCAST_ID = 1; + private static final String BROADCAST_TITLE = "title"; + private final Context mContext = ApplicationProvider.getApplicationContext(); + @Mock private AudioStreamPreference mPreference; + @Mock private AudioStreamsProgressCategoryController mController; + @Mock private AudioStreamsHelper mHelper; + @Mock private AudioStreamsRepository mRepository; + @Mock private AudioStreamsDashboardFragment mFragment; + @Mock private FragmentActivity mActivity; + private FakeFeatureFactory mFeatureFactory; private SourceAddedState mInstance; @Before public void setUp() { - mInstance = SourceAddedState.getInstance(); + when(mFragment.getActivity()).thenReturn(mActivity); + mFeatureFactory = FakeFeatureFactory.setupForTest(); + mInstance = new SourceAddedState(); + when(mPreference.getAudioStreamBroadcastId()).thenReturn(BROADCAST_ID); + when(mPreference.getTitle()).thenReturn(BROADCAST_TITLE); } @Test @@ -58,4 +102,59 @@ public class SourceAddedStateTest { assertThat(stateEnum) .isEqualTo(AudioStreamsProgressCategoryController.AudioStreamState.SOURCE_ADDED); } + + @Test + public void testPerformAction() { + mInstance.setAudioStreamsRepositoryForTesting(mRepository); + BluetoothLeBroadcastMetadata mockMetadata = mock(BluetoothLeBroadcastMetadata.class); + when(mRepository.getCachedMetadata(anyInt())).thenReturn(mockMetadata); + when(mPreference.getContext()).thenReturn(mContext); + when(mPreference.getSourceOriginForLogging()) + .thenReturn(SourceOriginForLogging.QR_CODE_SCAN_SETTINGS); + + mInstance.performAction(mPreference, mController, mHelper); + + verify(mRepository).saveMetadata(eq(mContext), eq(mockMetadata)); + verify(mFeatureFactory.metricsFeatureProvider) + .action( + eq(mContext), + eq(SettingsEnums.ACTION_AUDIO_STREAM_JOIN_SUCCEED), + eq(SourceOriginForLogging.QR_CODE_SCAN_SETTINGS.ordinal())); + verify(mHelper).startMediaService(eq(mContext), eq(BROADCAST_ID), eq(BROADCAST_TITLE)); + } + + @Test + public void testGetOnClickListener_startSubSettings() { + when(mController.getFragment()).thenReturn(mFragment); + when(mFragment.getMetricsCategory()).thenReturn(AUDIO_STREAM_MAIN); + + Preference.OnPreferenceClickListener listener = mInstance.getOnClickListener(mController); + assertThat(listener).isNotNull(); + + // mContext is not an Activity context, calling startActivity() from outside of an Activity + // context requires the FLAG_ACTIVITY_NEW_TASK flag, create a mock to avoid this + // AndroidRuntimeException. + Context activityContext = mock(Context.class); + when(mPreference.getContext()).thenReturn(activityContext); + + listener.onPreferenceClick(mPreference); + + ArgumentCaptor argumentCaptor = ArgumentCaptor.forClass(Intent.class); + verify(activityContext).startActivity(argumentCaptor.capture()); + + Intent intent = argumentCaptor.getValue(); + assertThat(intent.getStringExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT)) + .isEqualTo(AudioStreamDetailsFragment.class.getName()); + assertThat(intent.getIntExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT_TITLE_RESID, 0)) + .isEqualTo(R.string.audio_streams_detail_page_title); + assertThat(intent.getIntExtra(MetricsFeatureProvider.EXTRA_SOURCE_METRICS_CATEGORY, 0)) + .isEqualTo(AUDIO_STREAM_MAIN); + + Bundle bundle = intent.getBundleExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT_ARGUMENTS); + assertThat(bundle).isNotNull(); + assertThat(bundle.getString(AudioStreamDetailsFragment.BROADCAST_NAME_ARG)) + .isEqualTo(BROADCAST_TITLE); + assertThat(bundle.getInt(AudioStreamDetailsFragment.BROADCAST_ID_ARG)) + .isEqualTo(BROADCAST_ID); + } } diff --git a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/SyncedStateTest.java b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/SyncedStateTest.java index e9eab5066ac..2b19e2058b3 100644 --- a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/SyncedStateTest.java +++ b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/SyncedStateTest.java @@ -20,7 +20,7 @@ import static com.android.settings.connecteddevice.audiosharing.audiostreams.Aud import static com.google.common.truth.Truth.assertThat; -import static org.mockito.Mockito.never; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import static org.robolectric.shadows.ShadowLooper.shadowMainLooper; @@ -28,10 +28,16 @@ import static org.robolectric.shadows.ShadowLooper.shadowMainLooper; import android.app.AlertDialog; import android.bluetooth.BluetoothLeBroadcastMetadata; import android.content.Context; +import android.content.DialogInterface; +import android.widget.Button; +import android.widget.TextView; import androidx.preference.Preference; import androidx.test.core.app.ApplicationProvider; +import com.android.settings.R; +import com.android.settingslib.bluetooth.BluetoothLeBroadcastMetadataExt; + import org.junit.After; import org.junit.Before; import org.junit.Rule; @@ -42,7 +48,9 @@ import org.mockito.junit.MockitoJUnit; import org.mockito.junit.MockitoRule; import org.robolectric.RobolectricTestRunner; import org.robolectric.annotation.Config; +import org.robolectric.shadow.api.Shadow; import org.robolectric.shadows.ShadowAlertDialog; +import org.robolectric.shadows.ShadowLooper; @RunWith(RobolectricTestRunner.class) @Config( @@ -51,6 +59,10 @@ import org.robolectric.shadows.ShadowAlertDialog; }) public class SyncedStateTest { @Rule public final MockitoRule mMockitoRule = MockitoJUnit.rule(); + private static final String ENCRYPTED_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 BROADCAST_TITLE = "title"; @Mock private AudioStreamsProgressCategoryController mMockController; @Mock private AudioStreamPreference mMockPreference; @Mock private BluetoothLeBroadcastMetadata mMockMetadata; @@ -105,18 +117,47 @@ public class SyncedStateTest { @Test public void testGetOnClickListener_isEncrypted_passwordDialogShowing() { + when(mMockPreference.getAudioStreamMetadata()) + .thenReturn( + BluetoothLeBroadcastMetadataExt.INSTANCE.convertToBroadcastMetadata( + ENCRYPTED_METADATA)); + when(mMockPreference.getContext()).thenReturn(mMockContext); + when(mMockPreference.getTitle()).thenReturn(BROADCAST_TITLE); + Preference.OnPreferenceClickListener listener = mInstance.getOnClickListener(mMockController); - when(mMockPreference.getAudioStreamMetadata()).thenReturn(mMockMetadata); - when(mMockPreference.getContext()).thenReturn(mMockContext); - when(mMockMetadata.isEncrypted()).thenReturn(true); + assertThat(listener).isNotNull(); listener.onPreferenceClick(mMockPreference); shadowMainLooper().idle(); AlertDialog dialog = ShadowAlertDialog.getLatestAlertDialog(); + assertThat(dialog).isNotNull(); assertThat(dialog.isShowing()).isTrue(); - verify(mMockController, never()).handleSourceAddRequest(mMockPreference, mMockMetadata); + + Button neutralButton = dialog.getButton(DialogInterface.BUTTON_NEUTRAL); + assertThat(neutralButton).isNotNull(); + assertThat(neutralButton.getText().toString()) + .isEqualTo(mMockContext.getString(android.R.string.cancel)); + + Button positiveButton = dialog.getButton(DialogInterface.BUTTON_POSITIVE); + assertThat(positiveButton).isNotNull(); + assertThat(positiveButton.getText().toString()) + .isEqualTo( + mMockContext.getString(R.string.bluetooth_connect_access_dialog_positive)); + + positiveButton.callOnClick(); + ShadowLooper.idleMainLooper(); + verify(mMockController).handleSourceAddRequest(any(), any()); + + ShadowAlertDialog shadowDialog = Shadow.extract(dialog); + TextView title = shadowDialog.getView().findViewById(R.id.broadcast_name_text); + assertThat(title).isNotNull(); + assertThat(title.getText().toString()).isEqualTo(BROADCAST_TITLE); + assertThat(shadowDialog.getTitle().toString()) + .isEqualTo(mMockContext.getString(R.string.find_broadcast_password_dialog_title)); + + dialog.cancel(); } } diff --git a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/WaitForSyncStateTest.java b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/WaitForSyncStateTest.java index 3eb07a46f74..d97bf8fe58e 100644 --- a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/WaitForSyncStateTest.java +++ b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/WaitForSyncStateTest.java @@ -16,22 +16,39 @@ package com.android.settings.connecteddevice.audiosharing.audiostreams; +import static android.app.settings.SettingsEnums.DIALOG_AUDIO_STREAM_MAIN_WAIT_FOR_SYNC_TIMEOUT; + +import static com.android.settings.connecteddevice.audiosharing.audiostreams.AudioStreamsScanQrCodeController.REQUEST_SCAN_BT_BROADCAST_QR_CODE; import static com.android.settings.connecteddevice.audiosharing.audiostreams.WaitForSyncState.AUDIO_STREAM_WAIT_FOR_SYNC_STATE_SUMMARY; import static com.android.settings.connecteddevice.audiosharing.audiostreams.WaitForSyncState.WAIT_FOR_SYNC_TIMEOUT_MILLIS; import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.eq; +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 android.app.settings.SettingsEnums; import android.bluetooth.BluetoothLeBroadcastMetadata; +import android.content.Context; +import android.content.Intent; + +import androidx.test.core.app.ApplicationProvider; + +import com.android.settings.R; +import com.android.settings.SettingsActivity; +import com.android.settings.testutils.FakeFeatureFactory; +import com.android.settingslib.core.instrumentation.MetricsFeatureProvider; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.junit.MockitoJUnit; import org.mockito.junit.MockitoRule; @@ -43,15 +60,18 @@ import java.util.concurrent.TimeUnit; @RunWith(RobolectricTestRunner.class) public class WaitForSyncStateTest { @Rule public final MockitoRule mMockitoRule = MockitoJUnit.rule(); + private final Context mContext = spy(ApplicationProvider.getApplicationContext()); @Mock private AudioStreamPreference mMockPreference; @Mock private AudioStreamsProgressCategoryController mMockController; @Mock private AudioStreamsHelper mMockHelper; @Mock private BluetoothLeBroadcastMetadata mMockMetadata; + private FakeFeatureFactory mFeatureFactory; private WaitForSyncState mInstance; @Before public void setUp() { - mInstance = WaitForSyncState.getInstance(); + mFeatureFactory = FakeFeatureFactory.setupForTest(); + mInstance = new WaitForSyncState(); } @Test @@ -93,12 +113,49 @@ public class WaitForSyncStateTest { .thenReturn(AudioStreamsProgressCategoryController.AudioStreamState.WAIT_FOR_SYNC); when(mMockPreference.getAudioStreamBroadcastId()).thenReturn(1); when(mMockPreference.getAudioStreamMetadata()).thenReturn(mMockMetadata); + when(mMockPreference.getContext()).thenReturn(mContext); when(mMockPreference.getSourceOriginForLogging()) - .thenReturn(SourceOriginForLogging.UNKNOWN); + .thenReturn(SourceOriginForLogging.BROADCAST_SEARCH); + when(mMockController.getFragment()).thenReturn(mock(AudioStreamsDashboardFragment.class)); mInstance.performAction(mMockPreference, mMockController, mMockHelper); ShadowLooper.idleMainLooper(WAIT_FOR_SYNC_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS); verify(mMockController).handleSourceLost(1); + verify(mFeatureFactory.metricsFeatureProvider) + .action( + eq(mContext), + eq(SettingsEnums.ACTION_AUDIO_STREAM_JOIN_FAILED_WAIT_FOR_SYNC_TIMEOUT), + eq(SourceOriginForLogging.BROADCAST_SEARCH.ordinal())); + verify(mContext).getString(R.string.audio_streams_dialog_stream_is_not_available); + verify(mContext).getString(R.string.audio_streams_is_not_playing); + verify(mContext).getString(R.string.audio_streams_dialog_close); + verify(mContext).getString(R.string.audio_streams_dialog_retry); + } + + @Test + public void testLaunchQrCodeScanFragment() { + // mContext is not an Activity context, calling startActivity() from outside of an Activity + // context requires the FLAG_ACTIVITY_NEW_TASK flag, create a mock to avoid this + // AndroidRuntimeException. + Context activityContext = mock(Context.class); + AudioStreamsDashboardFragment fragment = mock(AudioStreamsDashboardFragment.class); + mInstance.launchQrCodeScanFragment(activityContext, fragment); + + ArgumentCaptor intentCaptor = ArgumentCaptor.forClass(Intent.class); + ArgumentCaptor requestCodeCaptor = ArgumentCaptor.forClass(Integer.class); + verify(fragment) + .startActivityForResult(intentCaptor.capture(), requestCodeCaptor.capture()); + + Intent intent = intentCaptor.getValue(); + assertThat(intent.getStringExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT)) + .isEqualTo(AudioStreamsQrCodeScanFragment.class.getName()); + assertThat(intent.getIntExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT_TITLE_RESID, 0)) + .isEqualTo(R.string.audio_streams_main_page_scan_qr_code_title); + assertThat(intent.getIntExtra(MetricsFeatureProvider.EXTRA_SOURCE_METRICS_CATEGORY, 0)) + .isEqualTo(DIALOG_AUDIO_STREAM_MAIN_WAIT_FOR_SYNC_TIMEOUT); + + int requestCode = requestCodeCaptor.getValue(); + assertThat(requestCode).isEqualTo(REQUEST_SCAN_BT_BROADCAST_QR_CODE); } }