From 44a0b59ad2742ac1bdc470db92b983abffaa6987 Mon Sep 17 00:00:00 2001 From: chelseahao Date: Fri, 21 Jun 2024 10:43:28 +0800 Subject: [PATCH] [Audiosharing] Listen to `onProfileConnectionStateChanged` of LE_AUDIO_BROADCAST_ASSISTANT to be more precise on device connection status upon bluetooth on/off. Also increase test coverage. 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: Ia78b1fe19bff3cb179794db1dc09374db13818d8 --- ...udioStreamsActiveDeviceSummaryUpdater.java | 29 ++-- .../AudioStreamsCategoryController.java | 12 +- .../AudioStreamsProgressCategoryCallback.java | 13 -- .../AudioStreamsScanQrCodeController.java | 11 +- .../AudioStreamStateHandlerTest.java | 143 ++++++++++++++++++ ...StreamsActiveDeviceSummaryUpdaterTest.java | 34 ++++- .../AudioStreamsCategoryControllerTest.java | 23 ++- .../AudioStreamsDialogFragmentTest.java | 85 +++++++++++ ...ioStreamsProgressCategoryCallbackTest.java | 140 +++++++++++++++++ .../AudioStreamsScanQrCodeControllerTest.java | 46 +++++- 10 files changed, 493 insertions(+), 43 deletions(-) create mode 100644 tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamStateHandlerTest.java create mode 100644 tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsDialogFragmentTest.java create mode 100644 tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsProgressCategoryCallbackTest.java diff --git a/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsActiveDeviceSummaryUpdater.java b/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsActiveDeviceSummaryUpdater.java index ab22b0702bc..47ee440694f 100644 --- a/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsActiveDeviceSummaryUpdater.java +++ b/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsActiveDeviceSummaryUpdater.java @@ -16,24 +16,22 @@ package com.android.settings.connecteddevice.audiosharing.audiostreams; +import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothProfile; import android.content.Context; import android.text.TextUtils; -import android.util.Log; +import androidx.annotation.NonNull; import androidx.annotation.Nullable; import com.android.settings.R; import com.android.settings.bluetooth.Utils; import com.android.settingslib.bluetooth.BluetoothCallback; -import com.android.settingslib.bluetooth.BluetoothUtils; import com.android.settingslib.bluetooth.CachedBluetoothDevice; import com.android.settingslib.bluetooth.LocalBluetoothManager; import com.android.settingslib.utils.ThreadUtils; public class AudioStreamsActiveDeviceSummaryUpdater implements BluetoothCallback { - private static final String TAG = "AudioStreamsActiveDeviceSummaryUpdater"; - private static final boolean DEBUG = BluetoothUtils.D; private final LocalBluetoothManager mBluetoothManager; private Context mContext; @Nullable private String mSummary; @@ -47,17 +45,20 @@ public class AudioStreamsActiveDeviceSummaryUpdater implements BluetoothCallback } @Override - public void onActiveDeviceChanged( - @Nullable CachedBluetoothDevice activeDevice, int bluetoothProfile) { - if (DEBUG) { - Log.d( - TAG, - "onActiveDeviceChanged() with activeDevice : " - + (activeDevice == null ? "null" : activeDevice.getAddress()) - + " on profile : " - + bluetoothProfile); + public void onBluetoothStateChanged(@AdapterState int bluetoothState) { + if (bluetoothState == BluetoothAdapter.STATE_OFF) { + notifyChangeIfNeeded(); } - if (bluetoothProfile == BluetoothProfile.LE_AUDIO) { + } + + @Override + public void onProfileConnectionStateChanged( + @NonNull CachedBluetoothDevice cachedDevice, + @ConnectionState int state, + int bluetoothProfile) { + if (bluetoothProfile == BluetoothProfile.LE_AUDIO_BROADCAST_ASSISTANT + && (state == BluetoothAdapter.STATE_CONNECTED + || state == BluetoothAdapter.STATE_DISCONNECTED)) { notifyChangeIfNeeded(); } } diff --git a/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsCategoryController.java b/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsCategoryController.java index 3174ace8520..0107c6ee49b 100644 --- a/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsCategoryController.java +++ b/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsCategoryController.java @@ -16,12 +16,12 @@ package com.android.settings.connecteddevice.audiosharing.audiostreams; +import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothProfile; import android.content.Context; import android.util.Log; import androidx.annotation.NonNull; -import androidx.annotation.Nullable; import androidx.lifecycle.LifecycleOwner; import com.android.settings.bluetooth.Utils; @@ -44,9 +44,13 @@ public class AudioStreamsCategoryController extends AudioSharingBasePreferenceCo private final BluetoothCallback mBluetoothCallback = new BluetoothCallback() { @Override - public void onActiveDeviceChanged( - @Nullable CachedBluetoothDevice activeDevice, int bluetoothProfile) { - if (bluetoothProfile == BluetoothProfile.LE_AUDIO) { + public void onProfileConnectionStateChanged( + @NonNull CachedBluetoothDevice cachedDevice, + @ConnectionState int state, + int bluetoothProfile) { + if (bluetoothProfile == BluetoothProfile.LE_AUDIO_BROADCAST_ASSISTANT + && (state == BluetoothAdapter.STATE_CONNECTED + || state == BluetoothAdapter.STATE_DISCONNECTED)) { updateVisibility(); } } diff --git a/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsProgressCategoryCallback.java b/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsProgressCategoryCallback.java index cb3a0daac2f..3370d8dbfd5 100644 --- a/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsProgressCategoryCallback.java +++ b/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsProgressCategoryCallback.java @@ -19,7 +19,6 @@ package com.android.settings.connecteddevice.audiosharing.audiostreams; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothLeBroadcastMetadata; import android.bluetooth.BluetoothLeBroadcastReceiveState; -import android.util.Log; public class AudioStreamsProgressCategoryCallback extends AudioStreamsBroadcastAssistantCallback { private static final String TAG = "AudioStreamsProgressCategoryCallback"; @@ -53,10 +52,6 @@ public class AudioStreamsProgressCategoryCallback extends AudioStreamsBroadcastA @Override public void onSearchStarted(int reason) { super.onSearchStarted(reason); - if (mCategoryController == null) { - Log.w(TAG, "onSearchStarted() : mCategoryController is null!"); - return; - } mCategoryController.setScanning(true); } @@ -69,10 +64,6 @@ public class AudioStreamsProgressCategoryCallback extends AudioStreamsBroadcastA @Override public void onSearchStopped(int reason) { super.onSearchStopped(reason); - if (mCategoryController == null) { - Log.w(TAG, "onSearchStopped() : mCategoryController is null!"); - return; - } mCategoryController.setScanning(false); } @@ -86,10 +77,6 @@ public class AudioStreamsProgressCategoryCallback extends AudioStreamsBroadcastA @Override public void onSourceFound(BluetoothLeBroadcastMetadata source) { super.onSourceFound(source); - if (mCategoryController == null) { - Log.w(TAG, "onSourceFound() : mCategoryController is null!"); - return; - } mCategoryController.handleSourceFound(source); } diff --git a/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsScanQrCodeController.java b/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsScanQrCodeController.java index 5f50be72790..d0d82fbc678 100644 --- a/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsScanQrCodeController.java +++ b/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsScanQrCodeController.java @@ -16,6 +16,7 @@ package com.android.settings.connecteddevice.audiosharing.audiostreams; +import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothProfile; import android.content.Context; import android.util.Log; @@ -47,9 +48,13 @@ public class AudioStreamsScanQrCodeController extends BasePreferenceController final BluetoothCallback mBluetoothCallback = new BluetoothCallback() { @Override - public void onActiveDeviceChanged( - @Nullable CachedBluetoothDevice activeDevice, int bluetoothProfile) { - if (bluetoothProfile == BluetoothProfile.LE_AUDIO) { + public void onProfileConnectionStateChanged( + @NonNull CachedBluetoothDevice cachedDevice, + @ConnectionState int state, + int bluetoothProfile) { + if (bluetoothProfile == BluetoothProfile.LE_AUDIO_BROADCAST_ASSISTANT + && (state == BluetoothAdapter.STATE_CONNECTED + || state == BluetoothAdapter.STATE_DISCONNECTED)) { updateVisibility(); } } diff --git a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamStateHandlerTest.java b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamStateHandlerTest.java new file mode 100644 index 00000000000..adc77a183f6 --- /dev/null +++ b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamStateHandlerTest.java @@ -0,0 +1,143 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.connecteddevice.audiosharing.audiostreams; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +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 android.content.Context; + +import androidx.preference.Preference; +import androidx.test.core.app.ApplicationProvider; + +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 AudioStreamStateHandlerTest { + @Rule public final MockitoRule mMockitoRule = MockitoJUnit.rule(); + private static final int SUMMARY_RES = 1; + private static final String SUMMARY = "summary"; + private final Context mContext = spy(ApplicationProvider.getApplicationContext()); + @Mock private AudioStreamsProgressCategoryController mController; + @Mock private AudioStreamsHelper mHelper; + @Mock private AudioStreamPreference mPreference; + private AudioStreamStateHandler mHandler; + + @Before + public void setUp() { + mHandler = spy(new AudioStreamStateHandler()); + } + + @Test + public void testHandleStateChange_noChange_doNothing() { + when(mHandler.getStateEnum()) + .thenReturn( + AudioStreamsProgressCategoryController.AudioStreamState + .ADD_SOURCE_BAD_CODE); + when(mPreference.getAudioStreamState()) + .thenReturn( + AudioStreamsProgressCategoryController.AudioStreamState + .ADD_SOURCE_BAD_CODE); + + mHandler.handleStateChange(mPreference, mController, mHelper); + + verify(mPreference, never()).setAudioStreamState(any()); + verify(mHandler, never()).performAction(any(), any(), any()); + verify(mPreference, never()).setIsConnected(anyBoolean(), anyString(), any()); + } + + @Test + public void testHandleStateChange_setNewState() { + when(mHandler.getStateEnum()) + .thenReturn(AudioStreamsProgressCategoryController.AudioStreamState.SOURCE_ADDED); + when(mPreference.getAudioStreamState()) + .thenReturn( + AudioStreamsProgressCategoryController.AudioStreamState + .ADD_SOURCE_BAD_CODE); + + mHandler.handleStateChange(mPreference, mController, mHelper); + + verify(mPreference) + .setAudioStreamState( + AudioStreamsProgressCategoryController.AudioStreamState.SOURCE_ADDED); + verify(mHandler).performAction(any(), any(), any()); + verify(mPreference).setIsConnected(eq(true), eq(""), eq(null)); + } + + @Test + public void testHandleStateChange_setNewState_newSummary_newListener() { + Preference.OnPreferenceClickListener listener = + mock(Preference.OnPreferenceClickListener.class); + when(mHandler.getStateEnum()) + .thenReturn( + AudioStreamsProgressCategoryController.AudioStreamState + .ADD_SOURCE_BAD_CODE); + when(mHandler.getSummary()).thenReturn(SUMMARY_RES); + when(mHandler.getOnClickListener(any())).thenReturn(listener); + when(mPreference.getAudioStreamState()) + .thenReturn( + AudioStreamsProgressCategoryController.AudioStreamState.ADD_SOURCE_FAILED); + when(mPreference.getContext()).thenReturn(mContext); + doReturn(SUMMARY).when(mContext).getString(anyInt()); + + mHandler.handleStateChange(mPreference, mController, mHelper); + + verify(mPreference) + .setAudioStreamState( + AudioStreamsProgressCategoryController.AudioStreamState + .ADD_SOURCE_BAD_CODE); + verify(mHandler).performAction(any(), any(), any()); + verify(mPreference).setIsConnected(eq(false), eq(SUMMARY), eq(listener)); + } + + @Test + public void testGetSummary() { + int res = mHandler.getSummary(); + assertThat(res).isEqualTo(AudioStreamStateHandler.EMPTY_STRING_RES); + } + + @Test + public void testGetOnClickListener() { + Preference.OnPreferenceClickListener listener = mHandler.getOnClickListener(mController); + assertThat(listener).isNull(); + } + + @Test + public void testGetStateEnum() { + var state = mHandler.getStateEnum(); + assertThat(state) + .isEqualTo(AudioStreamsProgressCategoryController.AudioStreamState.UNKNOWN); + } +} diff --git a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsActiveDeviceSummaryUpdaterTest.java b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsActiveDeviceSummaryUpdaterTest.java index 4403528e2fb..d6b99a1d3e3 100644 --- a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsActiveDeviceSummaryUpdaterTest.java +++ b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsActiveDeviceSummaryUpdaterTest.java @@ -20,6 +20,7 @@ import static com.google.common.truth.Truth.assertThat; import static org.mockito.Mockito.when; +import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothProfile; import android.content.Context; @@ -76,25 +77,46 @@ public class AudioStreamsActiveDeviceSummaryUpdaterTest { } @Test - public void onActiveDeviceChanged_notLeProfile_doNothing() { - mUpdater.onActiveDeviceChanged(mCachedBluetoothDevice, 0); + public void unregister_doNothing() { + mUpdater.register(false); assertThat(mUpdatedSummary).isNull(); } @Test - public void onActiveDeviceChanged_leProfile_summaryUpdated() { + public void onProfileConnectionStateChanged_notLeAssistProfile_doNothing() { + mUpdater.onProfileConnectionStateChanged(mCachedBluetoothDevice, 0, 0); + + assertThat(mUpdatedSummary).isNull(); + } + + @Test + public void onProfileConnectionStateChanged_leAssistantProfile_summaryUpdated() { ShadowAudioStreamsHelper.setCachedBluetoothDeviceInSharingOrLeConnected( mCachedBluetoothDevice); when(mCachedBluetoothDevice.getName()).thenReturn(DEVICE_NAME); - mUpdater.onActiveDeviceChanged(mCachedBluetoothDevice, BluetoothProfile.LE_AUDIO); + mUpdater.onProfileConnectionStateChanged( + mCachedBluetoothDevice, + BluetoothAdapter.STATE_CONNECTED, + BluetoothProfile.LE_AUDIO_BROADCAST_ASSISTANT); assertThat(mUpdatedSummary).isEqualTo(DEVICE_NAME); } @Test - public void onActiveDeviceChanged_leProfile_noDevice_summaryUpdated() { - mUpdater.onActiveDeviceChanged(mCachedBluetoothDevice, BluetoothProfile.LE_AUDIO); + public void onActiveDeviceChanged_leAssistantProfile_noDevice_summaryUpdated() { + mUpdater.onProfileConnectionStateChanged( + mCachedBluetoothDevice, + BluetoothAdapter.STATE_CONNECTED, + BluetoothProfile.LE_AUDIO_BROADCAST_ASSISTANT); + + assertThat(mUpdatedSummary) + .isEqualTo(mContext.getString(R.string.audio_streams_dialog_no_le_device_title)); + } + + @Test + public void onBluetoothStateOff_summaryUpdated() { + mUpdater.onBluetoothStateChanged(BluetoothAdapter.STATE_OFF); assertThat(mUpdatedSummary) .isEqualTo(mContext.getString(R.string.audio_streams_dialog_no_le_device_title)); diff --git a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsCategoryControllerTest.java b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsCategoryControllerTest.java index e4b6903e800..0e003097a3f 100644 --- a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsCategoryControllerTest.java +++ b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsCategoryControllerTest.java @@ -23,11 +23,13 @@ import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.any; 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.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothProfile; import android.bluetooth.BluetoothStatusCodes; import android.content.Context; import android.os.Looper; @@ -42,6 +44,7 @@ import com.android.settings.bluetooth.Utils; import com.android.settings.connecteddevice.audiosharing.audiostreams.testshadows.ShadowAudioStreamsHelper; import com.android.settings.testutils.shadow.ShadowBluetoothAdapter; import com.android.settings.testutils.shadow.ShadowBluetoothUtils; +import com.android.settingslib.bluetooth.BluetoothCallback; import com.android.settingslib.bluetooth.BluetoothEventManager; import com.android.settingslib.bluetooth.CachedBluetoothDevice; import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcast; @@ -57,6 +60,7 @@ 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; @@ -116,7 +120,7 @@ public class AudioStreamsCategoryControllerTest { when(mBroadcast.isProfileReady()).thenReturn(true); when(mAssistant.isProfileReady()).thenReturn(true); when(mVolumeControl.isProfileReady()).thenReturn(true); - mController = new AudioStreamsCategoryController(mContext, KEY); + mController = spy(new AudioStreamsCategoryController(mContext, KEY)); mPreference = new Preference(mContext); when(mScreen.findPreference(KEY)).thenReturn(mPreference); mController.displayPreference(mScreen); @@ -228,4 +232,21 @@ public class AudioStreamsCategoryControllerTest { shadowOf(Looper.getMainLooper()).idle(); assertThat(mPreference.isVisible()).isTrue(); } + + @Test + public void onProfileConnectionStateChanged_updateVisibility() { + mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_QR_CODE_PRIVATE_BROADCAST_SHARING); + ArgumentCaptor argumentCaptor = + ArgumentCaptor.forClass(BluetoothCallback.class); + mController.onStart(mLifecycleOwner); + verify(mBluetoothEventManager).registerCallback(argumentCaptor.capture()); + + BluetoothCallback callback = argumentCaptor.getValue(); + callback.onProfileConnectionStateChanged( + mCachedBluetoothDevice, + BluetoothProfile.LE_AUDIO_BROADCAST_ASSISTANT, + BluetoothAdapter.STATE_DISCONNECTED); + + verify(mController).updateVisibility(); + } } diff --git a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsDialogFragmentTest.java b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsDialogFragmentTest.java new file mode 100644 index 00000000000..e83dade16e9 --- /dev/null +++ b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsDialogFragmentTest.java @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.connecteddevice.audiosharing.audiostreams; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; + +import android.app.settings.SettingsEnums; +import android.content.Context; +import android.os.Bundle; + +import androidx.test.core.app.ApplicationProvider; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.annotation.Config; +import org.robolectric.shadows.ShadowAlertDialog; +import org.robolectric.shadows.ShadowLooper; +import org.robolectric.shadows.androidx.fragment.FragmentController; + +@RunWith(RobolectricTestRunner.class) +@Config( + shadows = { + ShadowAlertDialog.class, + }) +public class AudioStreamsDialogFragmentTest { + private final Context mContext = ApplicationProvider.getApplicationContext(); + private AudioStreamsDialogFragment.DialogBuilder mDialogBuilder; + private AudioStreamsDialogFragment mFragment; + + @Before + public void setUp() { + mDialogBuilder = spy(new AudioStreamsDialogFragment.DialogBuilder(mContext)); + mFragment = new AudioStreamsDialogFragment(mDialogBuilder, SettingsEnums.PAGE_UNKNOWN); + } + + @After + public void tearDown() { + ShadowAlertDialog.reset(); + } + + @Test + public void testGetMetricsCategory() { + int dialogId = mFragment.getMetricsCategory(); + + assertThat(dialogId).isEqualTo(SettingsEnums.PAGE_UNKNOWN); + } + + @Test + public void testOnCreateDialog() { + mFragment.onCreateDialog(Bundle.EMPTY); + + verify(mDialogBuilder).build(); + } + + @Test + public void testShowDialog() { + FragmentController.setupFragment(mFragment); + AudioStreamsDialogFragment.show(mFragment, mDialogBuilder, SettingsEnums.PAGE_UNKNOWN); + ShadowLooper.idleMainLooper(); + + var dialog = ShadowAlertDialog.getLatestAlertDialog(); + assertThat(dialog).isNotNull(); + assertThat(dialog.isShowing()).isTrue(); + } +} diff --git a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsProgressCategoryCallbackTest.java b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsProgressCategoryCallbackTest.java new file mode 100644 index 00000000000..164c2f093e8 --- /dev/null +++ b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsProgressCategoryCallbackTest.java @@ -0,0 +1,140 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.connecteddevice.audiosharing.audiostreams; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.bluetooth.BluetoothDevice; +import android.bluetooth.BluetoothLeBroadcastMetadata; +import android.bluetooth.BluetoothLeBroadcastReceiveState; + +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; + +import java.util.ArrayList; +import java.util.List; + +@RunWith(RobolectricTestRunner.class) +public class AudioStreamsProgressCategoryCallbackTest { + @Rule public final MockitoRule mMockitoRule = MockitoJUnit.rule(); + @Mock private AudioStreamsProgressCategoryController mController; + @Mock private BluetoothDevice mDevice; + @Mock private BluetoothLeBroadcastReceiveState mState; + @Mock private BluetoothLeBroadcastMetadata mMetadata; + private AudioStreamsProgressCategoryCallback mCallback; + + @Before + public void setUp() { + mCallback = new AudioStreamsProgressCategoryCallback(mController); + } + + @Test + public void testOnReceiveStateChanged_connected() { + List bisSyncState = new ArrayList<>(); + bisSyncState.add(1L); + when(mState.getBisSyncState()).thenReturn(bisSyncState); + mCallback.onReceiveStateChanged(mDevice, /* sourceId= */ 0, mState); + + verify(mController).handleSourceConnected(any()); + } + + @Test + public void testOnReceiveStateChanged_badCode() { + when(mState.getPaSyncState()) + .thenReturn(BluetoothLeBroadcastReceiveState.PA_SYNC_STATE_SYNCHRONIZED); + when(mState.getBigEncryptionState()) + .thenReturn(BluetoothLeBroadcastReceiveState.BIG_ENCRYPTION_STATE_BAD_CODE); + mCallback.onReceiveStateChanged(mDevice, /* sourceId= */ 0, mState); + + verify(mController).handleSourceConnectBadCode(any()); + } + + @Test + public void testOnSearchStartFailed() { + mCallback.onSearchStartFailed(/* reason= */ 0); + + verify(mController).showToast(anyString()); + verify(mController).setScanning(anyBoolean()); + } + + @Test + public void testOnSearchStarted() { + mCallback.onSearchStarted(/* reason= */ 0); + + verify(mController).setScanning(anyBoolean()); + } + + @Test + public void testOnSearchStopFailed() { + mCallback.onSearchStopFailed(/* reason= */ 0); + + verify(mController).showToast(anyString()); + } + + @Test + public void testOnSearchStopped() { + mCallback.onSearchStopped(/* reason= */ 0); + + verify(mController).setScanning(anyBoolean()); + } + + @Test + public void testOnSourceAddFailed() { + when(mMetadata.getBroadcastId()).thenReturn(1); + mCallback.onSourceAddFailed(mDevice, mMetadata, /* reason= */ 0); + + verify(mController).handleSourceFailedToConnect(1); + } + + @Test + public void testOnSourceFound() { + mCallback.onSourceFound(mMetadata); + + verify(mController).handleSourceFound(mMetadata); + } + + @Test + public void testOnSourceLost() { + mCallback.onSourceLost(/* broadcastId= */ 1); + + verify(mController).handleSourceLost(1); + } + + @Test + public void testOnSourceRemoveFailed() { + mCallback.onSourceRemoveFailed(mDevice, /* sourceId= */ 0, /* reason= */ 0); + + verify(mController).showToast(anyString()); + } + + @Test + public void testOnSourceRemoved() { + mCallback.onSourceRemoved(mDevice, /* sourceId= */ 0, /* reason= */ 0); + + verify(mController).handleSourceRemoved(); + } +} diff --git a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsScanQrCodeControllerTest.java b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsScanQrCodeControllerTest.java index 4990f26ac22..a83cbf03379 100644 --- a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsScanQrCodeControllerTest.java +++ b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsScanQrCodeControllerTest.java @@ -16,29 +16,38 @@ package com.android.settings.connecteddevice.audiosharing.audiostreams; +import static android.app.settings.SettingsEnums.AUDIO_STREAM_MAIN; + +import static com.android.settings.connecteddevice.audiosharing.audiostreams.AudioStreamsScanQrCodeController.REQUEST_SCAN_BT_BROADCAST_QR_CODE; import static com.android.settings.core.BasePreferenceController.AVAILABLE; import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothProfile; import android.content.Context; +import android.content.Intent; import androidx.lifecycle.LifecycleOwner; import androidx.preference.Preference; import androidx.preference.PreferenceScreen; import androidx.test.core.app.ApplicationProvider; +import com.android.settings.R; +import com.android.settings.SettingsActivity; import com.android.settings.connecteddevice.audiosharing.audiostreams.testshadows.ShadowAudioStreamsHelper; import com.android.settings.testutils.shadow.ShadowBluetoothUtils; import com.android.settingslib.bluetooth.BluetoothEventManager; import com.android.settingslib.bluetooth.CachedBluetoothDevice; import com.android.settingslib.bluetooth.LocalBluetoothManager; +import com.android.settingslib.core.instrumentation.MetricsFeatureProvider; import com.android.settingslib.core.lifecycle.Lifecycle; import org.junit.After; @@ -46,6 +55,7 @@ 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; @@ -139,17 +149,46 @@ public class AudioStreamsScanQrCodeControllerTest { public void onPreferenceClick_hasFragment_launchSubSetting() { mController.displayPreference(mScreen); mController.setFragment(mFragment); + when(mFragment.getMetricsCategory()).thenReturn(AUDIO_STREAM_MAIN); var listener = mPreference.getOnPreferenceClickListener(); 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); + when(mPreference.getKey()).thenReturn(AudioStreamsScanQrCodeController.KEY); + var clicked = listener.onPreferenceClick(mPreference); + + ArgumentCaptor intentCaptor = ArgumentCaptor.forClass(Intent.class); + ArgumentCaptor requestCodeCaptor = ArgumentCaptor.forClass(Integer.class); + verify(mFragment) + .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(AUDIO_STREAM_MAIN); + + int requestCode = requestCodeCaptor.getValue(); + assertThat(requestCode).isEqualTo(REQUEST_SCAN_BT_BROADCAST_QR_CODE); + assertThat(clicked).isTrue(); } @Test public void updateVisibility_noConnected_invisible() { mController.displayPreference(mScreen); - mController.mBluetoothCallback.onActiveDeviceChanged(mDevice, BluetoothProfile.LE_AUDIO); + mController.mBluetoothCallback.onProfileConnectionStateChanged( + mDevice, + BluetoothAdapter.STATE_DISCONNECTED, + BluetoothProfile.LE_AUDIO_BROADCAST_ASSISTANT); assertThat(mPreference.isVisible()).isFalse(); } @@ -158,7 +197,10 @@ public class AudioStreamsScanQrCodeControllerTest { public void updateVisibility_hasConnected_visible() { mController.displayPreference(mScreen); ShadowAudioStreamsHelper.setCachedBluetoothDeviceInSharingOrLeConnected(mDevice); - mController.mBluetoothCallback.onActiveDeviceChanged(mDevice, BluetoothProfile.LE_AUDIO); + mController.mBluetoothCallback.onProfileConnectionStateChanged( + mDevice, + BluetoothAdapter.STATE_CONNECTED, + BluetoothProfile.LE_AUDIO_BROADCAST_ASSISTANT); assertThat(mPreference.isVisible()).isTrue(); }