1 /*
2  * Copyright (C) 2024 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.settings.connecteddevice.audiosharing;
18 
19 import static com.android.settings.connecteddevice.audiosharing.AudioSharingUtils.SETTINGS_KEY_FALLBACK_DEVICE_GROUP_ID;
20 import static com.android.settings.core.BasePreferenceController.AVAILABLE;
21 import static com.android.settings.core.BasePreferenceController.UNSUPPORTED_ON_DEVICE;
22 
23 import static com.google.common.truth.Truth.assertThat;
24 
25 import static org.mockito.ArgumentMatchers.any;
26 import static org.mockito.ArgumentMatchers.anyInt;
27 import static org.mockito.ArgumentMatchers.eq;
28 import static org.mockito.Mockito.never;
29 import static org.mockito.Mockito.verify;
30 import static org.mockito.Mockito.when;
31 import static org.robolectric.Shadows.shadowOf;
32 
33 import android.app.settings.SettingsEnums;
34 import android.bluetooth.BluetoothAdapter;
35 import android.bluetooth.BluetoothCsipSetCoordinator;
36 import android.bluetooth.BluetoothDevice;
37 import android.bluetooth.BluetoothLeBroadcastAssistant;
38 import android.bluetooth.BluetoothLeBroadcastMetadata;
39 import android.bluetooth.BluetoothLeBroadcastReceiveState;
40 import android.bluetooth.BluetoothProfile;
41 import android.bluetooth.BluetoothStatusCodes;
42 import android.content.ContentResolver;
43 import android.content.Context;
44 import android.database.ContentObserver;
45 import android.os.Looper;
46 import android.platform.test.flag.junit.SetFlagsRule;
47 import android.provider.Settings;
48 import android.view.View;
49 import android.widget.CheckedTextView;
50 
51 import androidx.appcompat.app.AlertDialog;
52 import androidx.fragment.app.Fragment;
53 import androidx.fragment.app.FragmentActivity;
54 import androidx.lifecycle.LifecycleOwner;
55 import androidx.preference.Preference;
56 import androidx.preference.PreferenceScreen;
57 import androidx.test.core.app.ApplicationProvider;
58 
59 import com.android.settings.R;
60 import com.android.settings.bluetooth.Utils;
61 import com.android.settings.testutils.FakeFeatureFactory;
62 import com.android.settings.testutils.shadow.ShadowAlertDialogCompat;
63 import com.android.settings.testutils.shadow.ShadowBluetoothAdapter;
64 import com.android.settings.testutils.shadow.ShadowBluetoothUtils;
65 import com.android.settings.testutils.shadow.ShadowThreadUtils;
66 import com.android.settingslib.bluetooth.BluetoothEventManager;
67 import com.android.settingslib.bluetooth.CachedBluetoothDevice;
68 import com.android.settingslib.bluetooth.CachedBluetoothDeviceManager;
69 import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcast;
70 import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant;
71 import com.android.settingslib.bluetooth.LocalBluetoothManager;
72 import com.android.settingslib.bluetooth.LocalBluetoothProfileManager;
73 import com.android.settingslib.bluetooth.VolumeControlProfile;
74 import com.android.settingslib.core.lifecycle.Lifecycle;
75 import com.android.settingslib.flags.Flags;
76 
77 import com.google.common.collect.ImmutableList;
78 import com.google.common.collect.ImmutableSet;
79 import com.google.common.collect.Iterables;
80 
81 import org.junit.After;
82 import org.junit.Before;
83 import org.junit.Rule;
84 import org.junit.Test;
85 import org.junit.runner.RunWith;
86 import org.mockito.Mock;
87 import org.mockito.Spy;
88 import org.mockito.junit.MockitoJUnit;
89 import org.mockito.junit.MockitoRule;
90 import org.robolectric.RobolectricTestRunner;
91 import org.robolectric.annotation.Config;
92 import org.robolectric.shadow.api.Shadow;
93 import org.robolectric.shadows.androidx.fragment.FragmentController;
94 
95 import java.util.ArrayList;
96 import java.util.List;
97 
98 @RunWith(RobolectricTestRunner.class)
99 @Config(
100         shadows = {
101             ShadowBluetoothAdapter.class,
102             ShadowBluetoothUtils.class,
103             ShadowThreadUtils.class,
104             ShadowAlertDialogCompat.class,
105         })
106 public class AudioSharingCallAudioPreferenceControllerTest {
107     private static final String PREF_KEY = "calls_and_alarms";
108     private static final String TEST_DEVICE_NAME1 = "test1";
109     private static final String TEST_DEVICE_NAME2 = "test2";
110     private static final int TEST_DEVICE_GROUP_ID1 = 1;
111     private static final int TEST_DEVICE_GROUP_ID2 = 2;
112 
113     private static final String TEST_SETTINGS_KEY =
114             "bluetooth_le_broadcast_fallback_active_group_id";
115 
116     @Rule public final MockitoRule mMockitoRule = MockitoJUnit.rule();
117     @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
118 
119     @Spy Context mContext = ApplicationProvider.getApplicationContext();
120     @Mock private PreferenceScreen mScreen;
121     @Mock private LocalBluetoothManager mLocalBtManager;
122     @Mock private BluetoothEventManager mBtEventManager;
123     @Mock private LocalBluetoothProfileManager mBtProfileManager;
124     @Mock private CachedBluetoothDeviceManager mCacheManager;
125     @Mock private LocalBluetoothLeBroadcast mBroadcast;
126     @Mock private LocalBluetoothLeBroadcastAssistant mAssistant;
127     @Mock private VolumeControlProfile mVolumeControl;
128     @Mock private BluetoothDevice mDevice1;
129     @Mock private BluetoothDevice mDevice2;
130     @Mock private BluetoothDevice mDevice3;
131     @Mock private CachedBluetoothDevice mCachedDevice1;
132     @Mock private CachedBluetoothDevice mCachedDevice2;
133     @Mock private CachedBluetoothDevice mCachedDevice3;
134     @Mock private BluetoothLeBroadcastReceiveState mState;
135     @Mock private BluetoothLeBroadcastMetadata mSource;
136     @Mock private ContentResolver mContentResolver;
137     private AudioSharingCallAudioPreferenceController mController;
138     @Spy private ContentObserver mContentObserver;
139     private ShadowBluetoothAdapter mShadowBluetoothAdapter;
140     private FakeFeatureFactory mFeatureFactory;
141     private Lifecycle mLifecycle;
142     private LifecycleOwner mLifecycleOwner;
143     private Preference mPreference;
144     private Fragment mParentFragment;
145 
146     @Before
setUp()147     public void setUp() {
148         ShadowAlertDialogCompat.reset();
149         mShadowBluetoothAdapter = Shadow.extract(BluetoothAdapter.getDefaultAdapter());
150         mShadowBluetoothAdapter.setEnabled(true);
151         mShadowBluetoothAdapter.setIsLeAudioBroadcastSourceSupported(
152                 BluetoothStatusCodes.FEATURE_SUPPORTED);
153         mShadowBluetoothAdapter.setIsLeAudioBroadcastAssistantSupported(
154                 BluetoothStatusCodes.FEATURE_SUPPORTED);
155         mLifecycleOwner = () -> mLifecycle;
156         mLifecycle = new Lifecycle(mLifecycleOwner);
157         mParentFragment = new Fragment();
158         FragmentController.setupFragment(
159                 mParentFragment,
160                 FragmentActivity.class,
161                 0 /* containerViewId */,
162                 null /* bundle */);
163         ShadowBluetoothUtils.sLocalBluetoothManager = mLocalBtManager;
164         LocalBluetoothManager btManager = Utils.getLocalBtManager(mContext);
165         mFeatureFactory = FakeFeatureFactory.setupForTest();
166         when(btManager.getEventManager()).thenReturn(mBtEventManager);
167         when(btManager.getProfileManager()).thenReturn(mBtProfileManager);
168         when(btManager.getCachedDeviceManager()).thenReturn(mCacheManager);
169         when(mBtProfileManager.getLeAudioBroadcastProfile()).thenReturn(mBroadcast);
170         when(mBtProfileManager.getLeAudioBroadcastAssistantProfile()).thenReturn(mAssistant);
171         when(mBtProfileManager.getVolumeControlProfile()).thenReturn(mVolumeControl);
172         when(mBroadcast.isProfileReady()).thenReturn(true);
173         when(mAssistant.isProfileReady()).thenReturn(true);
174         when(mVolumeControl.isProfileReady()).thenReturn(true);
175         List<Long> bisSyncState = new ArrayList<>();
176         bisSyncState.add(1L);
177         when(mState.getBisSyncState()).thenReturn(bisSyncState);
178         when(mContext.getContentResolver()).thenReturn(mContentResolver);
179         mController = new AudioSharingCallAudioPreferenceController(mContext);
180         mController.init(null);
181         mContentObserver = mController.getSettingsObserver();
182         mPreference = new Preference(mContext);
183         when(mScreen.findPreference(PREF_KEY)).thenReturn(mPreference);
184     }
185 
186     @After
tearDown()187     public void tearDown() {
188         ShadowAlertDialogCompat.reset();
189         ShadowThreadUtils.reset();
190         ShadowBluetoothUtils.reset();
191     }
192 
193     @Test
onStart_flagOff_doNothing()194     public void onStart_flagOff_doNothing() {
195         mSetFlagsRule.disableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
196         mController.onStart(mLifecycleOwner);
197         verify(mBtEventManager, never()).registerCallback(mController);
198         verify(mContentResolver, never())
199                 .registerContentObserver(
200                         Settings.Secure.getUriFor(SETTINGS_KEY_FALLBACK_DEVICE_GROUP_ID),
201                         false,
202                         mContentObserver);
203         verify(mAssistant, never())
204                 .registerServiceCallBack(any(), any(BluetoothLeBroadcastAssistant.Callback.class));
205     }
206 
207     @Test
onStart_flagOn_registerCallback()208     public void onStart_flagOn_registerCallback() {
209         mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
210         mController.onStart(mLifecycleOwner);
211         verify(mBtEventManager).registerCallback(mController);
212         verify(mContentResolver)
213                 .registerContentObserver(
214                         Settings.Secure.getUriFor(SETTINGS_KEY_FALLBACK_DEVICE_GROUP_ID),
215                         false,
216                         mContentObserver);
217         verify(mAssistant)
218                 .registerServiceCallBack(any(), any(BluetoothLeBroadcastAssistant.Callback.class));
219     }
220 
221     @Test
onStop_flagOff_doNothing()222     public void onStop_flagOff_doNothing() {
223         mSetFlagsRule.disableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
224         mController.setCallbacksRegistered(true);
225         mController.onStop(mLifecycleOwner);
226         verify(mBtEventManager, never()).unregisterCallback(mController);
227         verify(mContentResolver, never()).unregisterContentObserver(mContentObserver);
228         verify(mAssistant, never())
229                 .unregisterServiceCallBack(any(BluetoothLeBroadcastAssistant.Callback.class));
230     }
231 
232     @Test
onStop_flagOn_notRegistered_doNothing()233     public void onStop_flagOn_notRegistered_doNothing() {
234         mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
235         mController.setCallbacksRegistered(false);
236         mController.onStop(mLifecycleOwner);
237         verify(mBtEventManager, never()).unregisterCallback(mController);
238         verify(mContentResolver, never()).unregisterContentObserver(mContentObserver);
239         verify(mAssistant, never())
240                 .unregisterServiceCallBack(any(BluetoothLeBroadcastAssistant.Callback.class));
241     }
242 
243     @Test
onStop_flagOn_registered_unregisterCallback()244     public void onStop_flagOn_registered_unregisterCallback() {
245         mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
246         mController.setCallbacksRegistered(true);
247         mController.onStop(mLifecycleOwner);
248         verify(mBtEventManager).unregisterCallback(mController);
249         verify(mContentResolver).unregisterContentObserver(mContentObserver);
250         verify(mAssistant)
251                 .unregisterServiceCallBack(any(BluetoothLeBroadcastAssistant.Callback.class));
252     }
253 
254     @Test
getAvailabilityStatus_flagOn()255     public void getAvailabilityStatus_flagOn() {
256         mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
257         assertThat(mController.getAvailabilityStatus()).isEqualTo(AVAILABLE);
258     }
259 
260     @Test
getAvailabilityStatus_flagOff()261     public void getAvailabilityStatus_flagOff() {
262         mSetFlagsRule.disableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
263         assertThat(mController.getAvailabilityStatus()).isEqualTo(UNSUPPORTED_ON_DEVICE);
264     }
265 
266     @Test
updateVisibility_flagOff_invisible()267     public void updateVisibility_flagOff_invisible() {
268         mSetFlagsRule.disableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
269         when(mBroadcast.isEnabled(any())).thenReturn(true);
270         mController.displayPreference(mScreen);
271         mController.updateVisibility();
272         shadowOf(Looper.getMainLooper()).idle();
273         assertThat(mPreference.isVisible()).isFalse();
274     }
275 
276     @Test
updateVisibility_broadcastOffBluetoothOff_invisible()277     public void updateVisibility_broadcastOffBluetoothOff_invisible() {
278         mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
279         when(mBroadcast.isEnabled(any())).thenReturn(false);
280         mShadowBluetoothAdapter.setEnabled(false);
281         mController.displayPreference(mScreen);
282         mController.updateVisibility();
283         shadowOf(Looper.getMainLooper()).idle();
284         assertThat(mPreference.isVisible()).isFalse();
285     }
286 
287     @Test
updateVisibility_broadcastOnBluetoothOff_invisible()288     public void updateVisibility_broadcastOnBluetoothOff_invisible() {
289         mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
290         when(mBroadcast.isEnabled(any())).thenReturn(true);
291         mShadowBluetoothAdapter.setEnabled(false);
292         mController.displayPreference(mScreen);
293         mController.updateVisibility();
294         shadowOf(Looper.getMainLooper()).idle();
295         assertThat(mPreference.isVisible()).isFalse();
296     }
297 
298     @Test
updateVisibility_broadcastOffBluetoothOn_invisible()299     public void updateVisibility_broadcastOffBluetoothOn_invisible() {
300         mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
301         when(mBroadcast.isEnabled(any())).thenReturn(false);
302         mController.displayPreference(mScreen);
303         mController.updateVisibility();
304         shadowOf(Looper.getMainLooper()).idle();
305         assertThat(mPreference.isVisible()).isFalse();
306     }
307 
308     @Test
updateVisibility_broadcastOnBluetoothOn_visible()309     public void updateVisibility_broadcastOnBluetoothOn_visible() {
310         mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
311         when(mBroadcast.isEnabled(any())).thenReturn(true);
312         mController.displayPreference(mScreen);
313         mController.updateVisibility();
314         shadowOf(Looper.getMainLooper()).idle();
315         assertThat(mPreference.isVisible()).isTrue();
316     }
317 
318     @Test
onProfileConnectionStateChanged_noDeviceInSharing_updateSummary()319     public void onProfileConnectionStateChanged_noDeviceInSharing_updateSummary() {
320         Settings.Secure.putInt(mContentResolver, TEST_SETTINGS_KEY, TEST_DEVICE_GROUP_ID1);
321         when(mBroadcast.isEnabled(any())).thenReturn(true);
322         when(mAssistant.getDevicesMatchingConnectionStates(
323                         new int[] {BluetoothProfile.STATE_CONNECTED}))
324                 .thenReturn(ImmutableList.of());
325         mController.displayPreference(mScreen);
326         mPreference.setSummary("test");
327         mController.onProfileConnectionStateChanged(
328                 mCachedDevice1,
329                 BluetoothAdapter.STATE_DISCONNECTED,
330                 BluetoothProfile.LE_AUDIO_BROADCAST_ASSISTANT);
331         shadowOf(Looper.getMainLooper()).idle();
332         assertThat(mPreference.getSummary().toString()).isEmpty();
333     }
334 
335     @Test
onFallbackDeviceChanged_updateSummary()336     public void onFallbackDeviceChanged_updateSummary() {
337         Settings.Secure.putInt(mContentResolver, TEST_SETTINGS_KEY, TEST_DEVICE_GROUP_ID1);
338         when(mCachedDevice1.getGroupId()).thenReturn(TEST_DEVICE_GROUP_ID1);
339         when(mCachedDevice1.getDevice()).thenReturn(mDevice1);
340         when(mCachedDevice1.getName()).thenReturn(TEST_DEVICE_NAME1);
341         when(mCacheManager.findDevice(mDevice1)).thenReturn(mCachedDevice1);
342         when(mBroadcast.isEnabled(any())).thenReturn(true);
343         when(mAssistant.getDevicesMatchingConnectionStates(
344                         new int[] {BluetoothProfile.STATE_CONNECTED}))
345                 .thenReturn(ImmutableList.of(mDevice1));
346         when(mAssistant.getAllSources(any())).thenReturn(ImmutableList.of(mState));
347         mController.displayPreference(mScreen);
348         mContentObserver.onChange(true);
349         shadowOf(Looper.getMainLooper()).idle();
350         assertThat(mPreference.getSummary().toString())
351                 .isEqualTo(
352                         mContext.getString(
353                                 R.string.audio_sharing_call_audio_description, TEST_DEVICE_NAME1));
354     }
355 
356     @Test
displayPreference_showCorrectSummary()357     public void displayPreference_showCorrectSummary() {
358         Settings.Secure.putInt(mContentResolver, TEST_SETTINGS_KEY, TEST_DEVICE_GROUP_ID1);
359         when(mCachedDevice1.getGroupId()).thenReturn(TEST_DEVICE_GROUP_ID1);
360         when(mCachedDevice1.getDevice()).thenReturn(mDevice1);
361         when(mCachedDevice2.getGroupId()).thenReturn(TEST_DEVICE_GROUP_ID1);
362         when(mCachedDevice2.getDevice()).thenReturn(mDevice2);
363         when(mCachedDevice1.getMemberDevice()).thenReturn(ImmutableSet.of(mCachedDevice2));
364         when(mCachedDevice1.getName()).thenReturn(TEST_DEVICE_NAME1);
365         when(mCachedDevice3.getGroupId()).thenReturn(TEST_DEVICE_GROUP_ID2);
366         when(mCachedDevice3.getDevice()).thenReturn(mDevice3);
367         when(mCachedDevice3.getName()).thenReturn(TEST_DEVICE_NAME2);
368         when(mCacheManager.findDevice(mDevice1)).thenReturn(mCachedDevice1);
369         when(mCacheManager.findDevice(mDevice2)).thenReturn(mCachedDevice2);
370         when(mCacheManager.findDevice(mDevice3)).thenReturn(mCachedDevice3);
371         when(mBroadcast.isEnabled(any())).thenReturn(true);
372         when(mAssistant.getDevicesMatchingConnectionStates(
373                         new int[] {BluetoothProfile.STATE_CONNECTED}))
374                 .thenReturn(ImmutableList.of(mDevice1, mDevice2, mDevice3));
375         when(mAssistant.getAllSources(any())).thenReturn(ImmutableList.of(mState));
376         mController.displayPreference(mScreen);
377         shadowOf(Looper.getMainLooper()).idle();
378         assertThat(mPreference.getSummary().toString())
379                 .isEqualTo(
380                         mContext.getString(
381                                 R.string.audio_sharing_call_audio_description, TEST_DEVICE_NAME1));
382     }
383 
384     @Test
displayPreference_noFallbackDeviceInSharing_showEmptySummary()385     public void displayPreference_noFallbackDeviceInSharing_showEmptySummary() {
386         Settings.Secure.putInt(mContentResolver, TEST_SETTINGS_KEY, TEST_DEVICE_GROUP_ID2);
387         when(mCachedDevice1.getGroupId()).thenReturn(TEST_DEVICE_GROUP_ID1);
388         when(mCachedDevice1.getDevice()).thenReturn(mDevice1);
389         when(mCachedDevice1.getName()).thenReturn(TEST_DEVICE_NAME1);
390         when(mCacheManager.findDevice(mDevice1)).thenReturn(mCachedDevice1);
391         when(mBroadcast.isEnabled(any())).thenReturn(true);
392         when(mAssistant.getDevicesMatchingConnectionStates(
393                         new int[] {BluetoothProfile.STATE_CONNECTED}))
394                 .thenReturn(ImmutableList.of(mDevice1));
395         when(mAssistant.getAllSources(any())).thenReturn(ImmutableList.of(mState));
396         mController.displayPreference(mScreen);
397         shadowOf(Looper.getMainLooper()).idle();
398         assertThat(mPreference.getSummary().toString()).isEmpty();
399     }
400 
401     @Test
displayPreference_noFallbackDevice_showEmptySummary()402     public void displayPreference_noFallbackDevice_showEmptySummary() {
403         Settings.Secure.putInt(
404                 mContentResolver, TEST_SETTINGS_KEY, BluetoothCsipSetCoordinator.GROUP_ID_INVALID);
405         when(mBroadcast.isEnabled(any())).thenReturn(true);
406         when(mAssistant.getDevicesMatchingConnectionStates(
407                         new int[] {BluetoothProfile.STATE_CONNECTED}))
408                 .thenReturn(ImmutableList.of());
409         mController.displayPreference(mScreen);
410         shadowOf(Looper.getMainLooper()).idle();
411         assertThat(mPreference.getSummary().toString()).isEmpty();
412     }
413 
414     @Test
displayPreference_clickToShowCorrectDialog()415     public void displayPreference_clickToShowCorrectDialog() {
416         AlertDialog latestAlertDialog = ShadowAlertDialogCompat.getLatestAlertDialog();
417         if (latestAlertDialog != null) {
418             latestAlertDialog.dismiss();
419             ShadowAlertDialogCompat.reset();
420         }
421         Settings.Secure.putInt(mContentResolver, TEST_SETTINGS_KEY, TEST_DEVICE_GROUP_ID1);
422         when(mCachedDevice1.getGroupId()).thenReturn(TEST_DEVICE_GROUP_ID1);
423         when(mCachedDevice1.getName()).thenReturn(TEST_DEVICE_NAME1);
424         when(mCachedDevice1.getDevice()).thenReturn(mDevice1);
425         when(mCachedDevice2.getGroupId()).thenReturn(TEST_DEVICE_GROUP_ID2);
426         when(mCachedDevice2.getName()).thenReturn(TEST_DEVICE_NAME2);
427         when(mCachedDevice2.getDevice()).thenReturn(mDevice2);
428         when(mCacheManager.findDevice(mDevice1)).thenReturn(mCachedDevice1);
429         when(mCacheManager.findDevice(mDevice2)).thenReturn(mCachedDevice2);
430         mShadowBluetoothAdapter.setMostRecentlyConnectedDevices(List.of(mDevice1, mDevice2));
431         when(mBroadcast.isEnabled(any())).thenReturn(true);
432         when(mAssistant.getDevicesMatchingConnectionStates(
433                         new int[] {BluetoothProfile.STATE_CONNECTED}))
434                 .thenReturn(ImmutableList.of(mDevice1, mDevice2));
435         when(mAssistant.getAllSources(any())).thenReturn(ImmutableList.of(mState));
436         mController.init(mParentFragment);
437         mController.displayPreference(mScreen);
438         shadowOf(Looper.getMainLooper()).idle();
439         mPreference.performClick();
440         shadowOf(Looper.getMainLooper()).idle();
441         AlertDialog dialog = ShadowAlertDialogCompat.getLatestAlertDialog();
442         assertThat(dialog.isShowing()).isTrue();
443         assertThat(dialog.getListView().getCount()).isEqualTo(2);
444         ArrayList<View> outViews = new ArrayList<>();
445         dialog.getListView()
446                 .findViewsWithText(outViews, TEST_DEVICE_NAME1, View.FIND_VIEWS_WITH_TEXT);
447         assertThat(outViews.size()).isEqualTo(1);
448         View view = Iterables.getOnlyElement(outViews);
449         assertThat(view instanceof CheckedTextView).isTrue();
450         assertThat(((CheckedTextView) view).isChecked()).isTrue();
451         verify(mFeatureFactory.metricsFeatureProvider)
452                 .visible(
453                         /* context= */ eq(null),
454                         /* source= */ anyInt(),
455                         eq(SettingsEnums.DIALOG_AUDIO_SHARING_CALL_AUDIO),
456                         /* latency= */ anyInt());
457     }
458 
459     @Test
logCallAudioDeviceChange_changeCallAudioToEarlierConnectedDevice()460     public void logCallAudioDeviceChange_changeCallAudioToEarlierConnectedDevice() {
461         when(mCachedDevice1.getGroupId()).thenReturn(TEST_DEVICE_GROUP_ID1);
462         when(mCachedDevice1.getDevice()).thenReturn(mDevice1);
463         when(mCachedDevice2.getGroupId()).thenReturn(TEST_DEVICE_GROUP_ID2);
464         when(mCachedDevice2.getDevice()).thenReturn(mDevice2);
465         when(mCacheManager.findDevice(mDevice1)).thenReturn(mCachedDevice1);
466         when(mCacheManager.findDevice(mDevice2)).thenReturn(mCachedDevice2);
467         mShadowBluetoothAdapter.setMostRecentlyConnectedDevices(List.of(mDevice1, mDevice2));
468         mController.logCallAudioDeviceChange(TEST_DEVICE_GROUP_ID1, mCachedDevice2);
469         verify(mFeatureFactory.metricsFeatureProvider)
470                 .action(
471                         mContext,
472                         SettingsEnums.ACTION_AUDIO_SHARING_CHANGE_CALL_AUDIO,
473                         AudioSharingCallAudioPreferenceController.ChangeCallAudioType
474                                 .CONNECTED_EARLIER
475                                 .ordinal());
476     }
477 
478     @Test
logCallAudioDeviceChange_changeCallAudioToLaterConnectedDevice()479     public void logCallAudioDeviceChange_changeCallAudioToLaterConnectedDevice() {
480         when(mCachedDevice1.getGroupId()).thenReturn(TEST_DEVICE_GROUP_ID1);
481         when(mCachedDevice1.getDevice()).thenReturn(mDevice1);
482         when(mCachedDevice2.getGroupId()).thenReturn(TEST_DEVICE_GROUP_ID2);
483         when(mCachedDevice2.getDevice()).thenReturn(mDevice2);
484         when(mCacheManager.findDevice(mDevice1)).thenReturn(mCachedDevice1);
485         when(mCacheManager.findDevice(mDevice2)).thenReturn(mCachedDevice2);
486         mShadowBluetoothAdapter.setMostRecentlyConnectedDevices(List.of(mDevice1, mDevice2));
487         mController.logCallAudioDeviceChange(TEST_DEVICE_GROUP_ID2, mCachedDevice1);
488         verify(mFeatureFactory.metricsFeatureProvider)
489                 .action(
490                         mContext,
491                         SettingsEnums.ACTION_AUDIO_SHARING_CHANGE_CALL_AUDIO,
492                         AudioSharingCallAudioPreferenceController.ChangeCallAudioType
493                                 .CONNECTED_LATER
494                                 .ordinal());
495     }
496 
497     @Test
logCallAudioDeviceChange_deviceNotFoundInRecentList_unknownChangeType()498     public void logCallAudioDeviceChange_deviceNotFoundInRecentList_unknownChangeType() {
499         when(mCachedDevice1.getGroupId()).thenReturn(TEST_DEVICE_GROUP_ID1);
500         when(mCachedDevice1.getDevice()).thenReturn(mDevice1);
501         when(mCachedDevice2.getGroupId()).thenReturn(TEST_DEVICE_GROUP_ID2);
502         when(mCachedDevice2.getDevice()).thenReturn(mDevice2);
503         when(mCacheManager.findDevice(mDevice1)).thenReturn(mCachedDevice1);
504         when(mCacheManager.findDevice(mDevice2)).thenReturn(mCachedDevice2);
505         mShadowBluetoothAdapter.setMostRecentlyConnectedDevices(List.of(mDevice1));
506         mController.logCallAudioDeviceChange(TEST_DEVICE_GROUP_ID1, mCachedDevice2);
507         verify(mFeatureFactory.metricsFeatureProvider)
508                 .action(
509                         mContext,
510                         SettingsEnums.ACTION_AUDIO_SHARING_CHANGE_CALL_AUDIO,
511                         AudioSharingCallAudioPreferenceController.ChangeCallAudioType.UNKNOWN
512                                 .ordinal());
513     }
514 
515     @Test
testBluetoothLeBroadcastAssistantCallbacks_updateSummary()516     public void testBluetoothLeBroadcastAssistantCallbacks_updateSummary() {
517         when(mCachedDevice1.getGroupId()).thenReturn(TEST_DEVICE_GROUP_ID1);
518         when(mCachedDevice1.getDevice()).thenReturn(mDevice1);
519         when(mCachedDevice1.getName()).thenReturn(TEST_DEVICE_NAME1);
520         when(mCacheManager.findDevice(mDevice1)).thenReturn(mCachedDevice1);
521         Settings.Secure.putInt(
522                 mContentResolver, TEST_SETTINGS_KEY, BluetoothCsipSetCoordinator.GROUP_ID_INVALID);
523         when(mBroadcast.isEnabled(any())).thenReturn(true);
524         when(mAssistant.getDevicesMatchingConnectionStates(
525                         new int[] {BluetoothProfile.STATE_CONNECTED}))
526                 .thenReturn(ImmutableList.of());
527         mController.displayPreference(mScreen);
528         shadowOf(Looper.getMainLooper()).idle();
529         assertThat(mPreference.getSummary().toString()).isEmpty();
530 
531         // onReceiveStateChanged will update summary
532         Settings.Secure.putInt(mContentResolver, TEST_SETTINGS_KEY, TEST_DEVICE_GROUP_ID1);
533         when(mAssistant.getDevicesMatchingConnectionStates(
534                         new int[] {BluetoothProfile.STATE_CONNECTED}))
535                 .thenReturn(ImmutableList.of(mDevice1));
536         when(mAssistant.getAllSources(any())).thenReturn(ImmutableList.of(mState));
537         mController.mBroadcastAssistantCallback.onReceiveStateChanged(
538                 mDevice1, /* sourceId= */ 1, mState);
539         shadowOf(Looper.getMainLooper()).idle();
540         assertThat(mPreference.getSummary().toString())
541                 .isEqualTo(
542                         mContext.getString(
543                                 R.string.audio_sharing_call_audio_description, TEST_DEVICE_NAME1));
544     }
545 
546     @Test
testBluetoothLeBroadcastAssistantCallbacks_doNothing()547     public void testBluetoothLeBroadcastAssistantCallbacks_doNothing() {
548         when(mCachedDevice1.getGroupId()).thenReturn(TEST_DEVICE_GROUP_ID1);
549         when(mCachedDevice1.getDevice()).thenReturn(mDevice1);
550         when(mCachedDevice1.getName()).thenReturn(TEST_DEVICE_NAME1);
551         when(mCacheManager.findDevice(mDevice1)).thenReturn(mCachedDevice1);
552         Settings.Secure.putInt(
553                 mContentResolver, TEST_SETTINGS_KEY, BluetoothCsipSetCoordinator.GROUP_ID_INVALID);
554         when(mBroadcast.isEnabled(any())).thenReturn(true);
555         when(mAssistant.getDevicesMatchingConnectionStates(
556                         new int[] {BluetoothProfile.STATE_CONNECTED}))
557                 .thenReturn(ImmutableList.of());
558         mController.displayPreference(mScreen);
559         shadowOf(Looper.getMainLooper()).idle();
560         assertThat(mPreference.getSummary().toString()).isEmpty();
561 
562         Settings.Secure.putInt(mContentResolver, TEST_SETTINGS_KEY, TEST_DEVICE_GROUP_ID1);
563         when(mAssistant.getDevicesMatchingConnectionStates(
564                         new int[] {BluetoothProfile.STATE_CONNECTED}))
565                 .thenReturn(ImmutableList.of(mDevice1));
566         when(mAssistant.getAllSources(any())).thenReturn(ImmutableList.of(mState));
567         mController.mBroadcastAssistantCallback.onSearchStarted(/* reason= */ 1);
568         mController.mBroadcastAssistantCallback.onSearchStartFailed(/* reason= */ 1);
569         mController.mBroadcastAssistantCallback.onSearchStopped(/* reason= */ 1);
570         mController.mBroadcastAssistantCallback.onSearchStopFailed(/* reason= */ 1);
571         mController.mBroadcastAssistantCallback.onSourceAdded(
572                 mDevice1, /* sourceId= */ 1, /* reason= */ 1);
573         mController.mBroadcastAssistantCallback.onSourceAddFailed(
574                 mDevice1, mSource, /* reason= */ 1);
575         mController.mBroadcastAssistantCallback.onSourceRemoved(
576                 mDevice1, /* sourceId= */ 1, /* reason= */ 1);
577         mController.mBroadcastAssistantCallback.onSourceRemoveFailed(
578                 mDevice1, /* sourceId= */ 1, /* reason= */ 1);
579         mController.mBroadcastAssistantCallback.onSourceModified(
580                 mDevice1, /* sourceId= */ 1, /* reason= */ 1);
581         mController.mBroadcastAssistantCallback.onSourceModifyFailed(
582                 mDevice1, /* sourceId= */ 1, /* reason= */ 1);
583         mController.mBroadcastAssistantCallback.onSourceFound(mSource);
584         mController.mBroadcastAssistantCallback.onSourceLost(/* broadcastId= */ 1);
585         shadowOf(Looper.getMainLooper()).idle();
586 
587         // Above callbacks won't update summary.
588         assertThat(mPreference.getSummary().toString()).isEmpty();
589     }
590 }
591