1 /*
2  * Copyright 2018 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 package com.android.settings.connecteddevice;
17 
18 import static com.android.settings.core.BasePreferenceController.AVAILABLE_UNSEARCHABLE;
19 import static com.android.settings.core.BasePreferenceController.UNSUPPORTED_ON_DEVICE;
20 
21 import static com.google.common.truth.Truth.assertThat;
22 
23 import static org.mockito.ArgumentMatchers.any;
24 import static org.mockito.ArgumentMatchers.anyString;
25 import static org.mockito.Mockito.doReturn;
26 import static org.mockito.Mockito.spy;
27 import static org.mockito.Mockito.times;
28 import static org.mockito.Mockito.verify;
29 import static org.mockito.Mockito.when;
30 import static org.robolectric.Shadows.shadowOf;
31 import static org.robolectric.shadows.ShadowLooper.shadowMainLooper;
32 
33 import android.app.settings.SettingsEnums;
34 import android.bluetooth.BluetoothAdapter;
35 import android.bluetooth.BluetoothDevice;
36 import android.bluetooth.BluetoothLeBroadcast;
37 import android.bluetooth.BluetoothLeBroadcastAssistant;
38 import android.bluetooth.BluetoothProfile;
39 import android.bluetooth.BluetoothStatusCodes;
40 import android.content.Context;
41 import android.content.pm.PackageManager;
42 import android.graphics.drawable.Drawable;
43 import android.media.AudioManager;
44 import android.os.Looper;
45 import android.platform.test.flag.junit.SetFlagsRule;
46 import android.util.Pair;
47 
48 import androidx.appcompat.app.AlertDialog;
49 import androidx.fragment.app.FragmentActivity;
50 import androidx.fragment.app.FragmentManager;
51 import androidx.lifecycle.LifecycleOwner;
52 import androidx.preference.Preference;
53 import androidx.preference.PreferenceGroup;
54 import androidx.preference.PreferenceManager;
55 import androidx.preference.PreferenceScreen;
56 
57 import com.android.settings.R;
58 import com.android.settings.bluetooth.AvailableMediaBluetoothDeviceUpdater;
59 import com.android.settings.bluetooth.BluetoothDevicePreference;
60 import com.android.settings.bluetooth.Utils;
61 import com.android.settings.connecteddevice.audiosharing.AudioSharingDialogHandler;
62 import com.android.settings.testutils.FakeFeatureFactory;
63 import com.android.settings.testutils.shadow.ShadowAlertDialogCompat;
64 import com.android.settings.testutils.shadow.ShadowAudioManager;
65 import com.android.settings.testutils.shadow.ShadowBluetoothAdapter;
66 import com.android.settings.testutils.shadow.ShadowBluetoothUtils;
67 import com.android.settingslib.bluetooth.BluetoothCallback;
68 import com.android.settingslib.bluetooth.BluetoothEventManager;
69 import com.android.settingslib.bluetooth.CachedBluetoothDevice;
70 import com.android.settingslib.bluetooth.CachedBluetoothDeviceManager;
71 import com.android.settingslib.bluetooth.HearingAidInfo;
72 import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcast;
73 import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant;
74 import com.android.settingslib.bluetooth.LocalBluetoothManager;
75 import com.android.settingslib.bluetooth.LocalBluetoothProfileManager;
76 import com.android.settingslib.core.lifecycle.Lifecycle;
77 import com.android.settingslib.flags.Flags;
78 
79 import org.junit.Before;
80 import org.junit.Rule;
81 import org.junit.Test;
82 import org.junit.runner.RunWith;
83 import org.mockito.Answers;
84 import org.mockito.Mock;
85 import org.mockito.junit.MockitoJUnit;
86 import org.mockito.junit.MockitoRule;
87 import org.robolectric.Robolectric;
88 import org.robolectric.RobolectricTestRunner;
89 import org.robolectric.RuntimeEnvironment;
90 import org.robolectric.annotation.Config;
91 import org.robolectric.shadow.api.Shadow;
92 
93 import java.util.concurrent.Executor;
94 
95 /** Tests for {@link AvailableMediaDeviceGroupController}. */
96 @RunWith(RobolectricTestRunner.class)
97 @Config(
98         shadows = {
99             ShadowAudioManager.class,
100             ShadowBluetoothAdapter.class,
101             ShadowBluetoothUtils.class,
102             ShadowAlertDialogCompat.class,
103         })
104 public class AvailableMediaDeviceGroupControllerTest {
105     @Rule public final MockitoRule mMockitoRule = MockitoJUnit.rule();
106     @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
107 
108     private static final String TEST_DEVICE_ADDRESS = "00:A1:A1:A1:A1:A1";
109     private static final String PREFERENCE_KEY_1 = "pref_key_1";
110     private static final String TEST_DEVICE_NAME = "test";
111 
112     @Mock private AvailableMediaBluetoothDeviceUpdater mAvailableMediaBluetoothDeviceUpdater;
113     @Mock private PreferenceScreen mPreferenceScreen;
114 
115     @Mock(answer = Answers.RETURNS_DEEP_STUBS)
116     private PreferenceManager mPreferenceManager;
117 
118     @Mock private PackageManager mPackageManager;
119     @Mock private BluetoothEventManager mEventManager;
120     @Mock private LocalBluetoothManager mLocalBluetoothManager;
121     @Mock private LocalBluetoothProfileManager mLocalBtProfileManager;
122     @Mock private CachedBluetoothDeviceManager mCachedDeviceManager;
123     @Mock private LocalBluetoothLeBroadcast mBroadcast;
124     @Mock private LocalBluetoothLeBroadcastAssistant mAssistant;
125     @Mock private CachedBluetoothDevice mCachedBluetoothDevice;
126     @Mock private BluetoothDevice mDevice;
127     @Mock private Drawable mDrawable;
128     @Mock private AudioSharingDialogHandler mDialogHandler;
129 
130     private PreferenceGroup mPreferenceGroup;
131     private Context mContext;
132     private FragmentManager mFragManager;
133     private FakeFeatureFactory mFeatureFactory;
134     private Preference mPreference;
135     private AvailableMediaDeviceGroupController mAvailableMediaDeviceGroupController;
136     private AudioManager mAudioManager;
137     private ShadowBluetoothAdapter mShadowBluetoothAdapter;
138     private LifecycleOwner mLifecycleOwner;
139     private Lifecycle mLifecycle;
140 
141     @Before
setUp()142     public void setUp() {
143         mContext = spy(RuntimeEnvironment.application);
144         mLifecycleOwner = () -> mLifecycle;
145         mLifecycle = new Lifecycle(mLifecycleOwner);
146         mPreference = new Preference(mContext);
147         mPreference.setKey(PREFERENCE_KEY_1);
148         mPreferenceGroup = spy(new PreferenceScreen(mContext, null));
149         mFragManager =
150                 Robolectric.setupActivity(FragmentActivity.class).getSupportFragmentManager();
151         when(mPreferenceGroup.getPreferenceManager()).thenReturn(mPreferenceManager);
152         doReturn(mPackageManager).when(mContext).getPackageManager();
153         doReturn(true).when(mPackageManager).hasSystemFeature(PackageManager.FEATURE_BLUETOOTH);
154 
155         mShadowBluetoothAdapter = Shadow.extract(BluetoothAdapter.getDefaultAdapter());
156         mShadowBluetoothAdapter.setEnabled(true);
157         mShadowBluetoothAdapter.setIsLeAudioBroadcastSourceSupported(
158                 BluetoothStatusCodes.FEATURE_NOT_SUPPORTED);
159         mShadowBluetoothAdapter.setIsLeAudioBroadcastAssistantSupported(
160                 BluetoothStatusCodes.FEATURE_NOT_SUPPORTED);
161         ShadowBluetoothUtils.sLocalBluetoothManager = mLocalBluetoothManager;
162         mLocalBluetoothManager = Utils.getLocalBtManager(mContext);
163         mFeatureFactory = FakeFeatureFactory.setupForTest();
164         mAudioManager = mContext.getSystemService(AudioManager.class);
165         doReturn(mEventManager).when(mLocalBluetoothManager).getEventManager();
166         when(mLocalBluetoothManager.getProfileManager()).thenReturn(mLocalBtProfileManager);
167         when(mLocalBluetoothManager.getCachedDeviceManager()).thenReturn(mCachedDeviceManager);
168         when(mCachedDeviceManager.findDevice(any(BluetoothDevice.class)))
169                 .thenReturn(mCachedBluetoothDevice);
170         when(mCachedBluetoothDevice.getAddress()).thenReturn(TEST_DEVICE_ADDRESS);
171 
172         mAvailableMediaDeviceGroupController =
173                 spy(new AvailableMediaDeviceGroupController(mContext));
174         mAvailableMediaDeviceGroupController.setBluetoothDeviceUpdater(
175                 mAvailableMediaBluetoothDeviceUpdater);
176         mAvailableMediaDeviceGroupController.setDialogHandler(mDialogHandler);
177         mAvailableMediaDeviceGroupController.setFragmentManager(mFragManager);
178         mAvailableMediaDeviceGroupController.mPreferenceGroup = mPreferenceGroup;
179     }
180 
181     @Test
onDeviceAdded_firstAdd_becomeVisibleAndPreferenceAdded()182     public void onDeviceAdded_firstAdd_becomeVisibleAndPreferenceAdded() {
183         mAvailableMediaDeviceGroupController.onDeviceAdded(mPreference);
184 
185         assertThat(mPreferenceGroup.isVisible()).isTrue();
186         assertThat((Preference) mPreferenceGroup.findPreference(PREFERENCE_KEY_1))
187                 .isEqualTo(mPreference);
188     }
189 
190     @Test
onDeviceRemoved_lastRemove_becomeInvisibleAndPreferenceRemoved()191     public void onDeviceRemoved_lastRemove_becomeInvisibleAndPreferenceRemoved() {
192         mPreferenceGroup.addPreference(mPreference);
193 
194         mAvailableMediaDeviceGroupController.onDeviceRemoved(mPreference);
195 
196         assertThat(mPreferenceGroup.isVisible()).isFalse();
197         assertThat(mPreferenceGroup.getPreferenceCount()).isEqualTo(0);
198     }
199 
200     @Test
onDeviceRemoved_notLastRemove_stillVisible()201     public void onDeviceRemoved_notLastRemove_stillVisible() {
202         mPreferenceGroup.setVisible(true);
203         mPreferenceGroup.addPreference(mPreference);
204         mPreferenceGroup.addPreference(new Preference(mContext));
205 
206         mAvailableMediaDeviceGroupController.onDeviceRemoved(mPreference);
207 
208         assertThat(mPreferenceGroup.isVisible()).isTrue();
209     }
210 
211     @Test
displayPreference_becomeInvisible()212     public void displayPreference_becomeInvisible() {
213         doReturn(mPreferenceGroup).when(mPreferenceScreen).findPreference(anyString());
214 
215         mAvailableMediaDeviceGroupController.displayPreference(mPreferenceScreen);
216 
217         assertThat(mPreferenceGroup.isVisible()).isFalse();
218     }
219 
220     @Test
testRegister_audioSharingFlagOff()221     public void testRegister_audioSharingFlagOff() {
222         mSetFlagsRule.disableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
223         setUpBroadcast();
224         // register the callback in onStart()
225         mAvailableMediaDeviceGroupController.onStart(mLifecycleOwner);
226         shadowOf(Looper.getMainLooper()).idle();
227 
228         verify(mAvailableMediaBluetoothDeviceUpdater).registerCallback();
229         verify(mEventManager).registerCallback(any(BluetoothCallback.class));
230         verify(mAvailableMediaBluetoothDeviceUpdater).refreshPreference();
231         verify(mBroadcast, times(0))
232                 .registerServiceCallBack(
233                         any(Executor.class), any(BluetoothLeBroadcast.Callback.class));
234         verify(mAssistant, times(0))
235                 .registerServiceCallBack(
236                         any(Executor.class), any(BluetoothLeBroadcastAssistant.Callback.class));
237         verify(mDialogHandler, times(0)).registerCallbacks(any(Executor.class));
238     }
239 
240     @Test
testRegister_audioSharingFlagOn()241     public void testRegister_audioSharingFlagOn() {
242         mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
243         setUpBroadcast();
244         // register the callback in onStart()
245         mAvailableMediaDeviceGroupController.onStart(mLifecycleOwner);
246         shadowOf(Looper.getMainLooper()).idle();
247 
248         verify(mAvailableMediaBluetoothDeviceUpdater).registerCallback();
249         verify(mEventManager).registerCallback(any(BluetoothCallback.class));
250         verify(mAvailableMediaBluetoothDeviceUpdater).refreshPreference();
251         verify(mBroadcast)
252                 .registerServiceCallBack(
253                         any(Executor.class), any(BluetoothLeBroadcast.Callback.class));
254         verify(mAssistant)
255                 .registerServiceCallBack(
256                         any(Executor.class), any(BluetoothLeBroadcastAssistant.Callback.class));
257         verify(mDialogHandler).registerCallbacks(any(Executor.class));
258     }
259 
260     @Test
testUnregister_audioSharingFlagOff()261     public void testUnregister_audioSharingFlagOff() {
262         mSetFlagsRule.disableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
263         setUpBroadcast();
264         // unregister the callback in onStop()
265         mAvailableMediaDeviceGroupController.onStop(mLifecycleOwner);
266         verify(mAvailableMediaBluetoothDeviceUpdater).unregisterCallback();
267         verify(mEventManager).unregisterCallback(any(BluetoothCallback.class));
268         verify(mBroadcast, times(0))
269                 .unregisterServiceCallBack(any(BluetoothLeBroadcast.Callback.class));
270         verify(mAssistant, times(0))
271                 .unregisterServiceCallBack(any(BluetoothLeBroadcastAssistant.Callback.class));
272         verify(mDialogHandler, times(0)).unregisterCallbacks();
273     }
274 
275     @Test
testUnregister_audioSharingFlagOn()276     public void testUnregister_audioSharingFlagOn() {
277         mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
278         setUpBroadcast();
279         // unregister the callback in onStop()
280         mAvailableMediaDeviceGroupController.onStop(mLifecycleOwner);
281         verify(mAvailableMediaBluetoothDeviceUpdater).unregisterCallback();
282         verify(mEventManager).unregisterCallback(any(BluetoothCallback.class));
283         verify(mBroadcast).unregisterServiceCallBack(any(BluetoothLeBroadcast.Callback.class));
284         verify(mAssistant)
285                 .unregisterServiceCallBack(any(BluetoothLeBroadcastAssistant.Callback.class));
286         verify(mDialogHandler).unregisterCallbacks();
287     }
288 
289     @Test
testGetAvailabilityStatus_noBluetoothFeature_returnUnSupported()290     public void testGetAvailabilityStatus_noBluetoothFeature_returnUnSupported() {
291         doReturn(false).when(mPackageManager).hasSystemFeature(PackageManager.FEATURE_BLUETOOTH);
292 
293         assertThat(mAvailableMediaDeviceGroupController.getAvailabilityStatus())
294                 .isEqualTo(UNSUPPORTED_ON_DEVICE);
295     }
296 
297     @Test
testGetAvailabilityStatus_BluetoothFeature_returnSupported()298     public void testGetAvailabilityStatus_BluetoothFeature_returnSupported() {
299         doReturn(true).when(mPackageManager).hasSystemFeature(PackageManager.FEATURE_BLUETOOTH);
300 
301         assertThat(mAvailableMediaDeviceGroupController.getAvailabilityStatus())
302                 .isEqualTo(AVAILABLE_UNSEARCHABLE);
303     }
304 
305     @Test
setTitle_inCallState_showCallStateTitle()306     public void setTitle_inCallState_showCallStateTitle() {
307         mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
308         setUpBroadcast();
309         mAudioManager.setMode(AudioManager.MODE_IN_CALL);
310         when(mBroadcast.isEnabled(null)).thenReturn(true);
311         mAvailableMediaDeviceGroupController.onAudioModeChanged();
312         shadowOf(Looper.getMainLooper()).idle();
313 
314         assertThat(mPreferenceGroup.getTitle().toString())
315                 .isEqualTo(mContext.getString(R.string.connected_device_call_device_title));
316     }
317 
318     @Test
setTitle_notInCallState_notInAudioSharing_showMediaStateTitle()319     public void setTitle_notInCallState_notInAudioSharing_showMediaStateTitle() {
320         mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
321         setUpBroadcast();
322         mAudioManager.setMode(AudioManager.MODE_NORMAL);
323         when(mBroadcast.isEnabled(null)).thenReturn(false);
324         mAvailableMediaDeviceGroupController.onAudioModeChanged();
325         shadowOf(Looper.getMainLooper()).idle();
326 
327         assertThat(mPreferenceGroup.getTitle().toString())
328                 .isEqualTo(mContext.getString(R.string.connected_device_media_device_title));
329     }
330 
331     @Test
setTitle_notInCallState_audioSharingFlagOff_showMediaStateTitle()332     public void setTitle_notInCallState_audioSharingFlagOff_showMediaStateTitle() {
333         mSetFlagsRule.disableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
334         setUpBroadcast();
335         mAudioManager.setMode(AudioManager.MODE_NORMAL);
336         when(mBroadcast.isEnabled(null)).thenReturn(true);
337         mAvailableMediaDeviceGroupController.onAudioModeChanged();
338         shadowOf(Looper.getMainLooper()).idle();
339 
340         assertThat(mPreferenceGroup.getTitle().toString())
341                 .isEqualTo(mContext.getString(R.string.connected_device_media_device_title));
342     }
343 
344     @Test
setTitle_notInCallState_inAudioSharing_showAudioSharingMediaStateTitle()345     public void setTitle_notInCallState_inAudioSharing_showAudioSharingMediaStateTitle() {
346         mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
347         setUpBroadcast();
348         mAudioManager.setMode(AudioManager.MODE_NORMAL);
349         when(mBroadcast.isEnabled(null)).thenReturn(true);
350         mAvailableMediaDeviceGroupController.onAudioModeChanged();
351         shadowOf(Looper.getMainLooper()).idle();
352 
353         assertThat(mPreferenceGroup.getTitle().toString())
354                 .isEqualTo(mContext.getString(R.string.audio_sharing_media_device_group_title));
355     }
356 
357     @Test
onStart_localBluetoothManagerNull_shouldNotCrash()358     public void onStart_localBluetoothManagerNull_shouldNotCrash() {
359         mAvailableMediaDeviceGroupController.mBtManager = null;
360 
361         // Shouldn't crash
362         mAvailableMediaDeviceGroupController.onStart(mLifecycleOwner);
363     }
364 
365     @Test
onStop_localBluetoothManagerNull_shouldNotCrash()366     public void onStop_localBluetoothManagerNull_shouldNotCrash() {
367         mAvailableMediaDeviceGroupController.mBtManager = null;
368 
369         // Shouldn't crash
370         mAvailableMediaDeviceGroupController.onStop(mLifecycleOwner);
371     }
372 
373     @Test
onActiveDeviceChanged_hearingAidProfile_launchHearingAidPairingDialog()374     public void onActiveDeviceChanged_hearingAidProfile_launchHearingAidPairingDialog() {
375         when(mCachedBluetoothDevice.isConnectedAshaHearingAidDevice()).thenReturn(true);
376         when(mCachedBluetoothDevice.getDeviceMode())
377                 .thenReturn(HearingAidInfo.DeviceMode.MODE_BINAURAL);
378         when(mCachedBluetoothDevice.getDeviceSide())
379                 .thenReturn(HearingAidInfo.DeviceSide.SIDE_LEFT);
380 
381         mAvailableMediaDeviceGroupController.onActiveDeviceChanged(
382                 mCachedBluetoothDevice, BluetoothProfile.HEARING_AID);
383         shadowMainLooper().idle();
384 
385         final AlertDialog dialog = ShadowAlertDialogCompat.getLatestAlertDialog();
386         assertThat(dialog.isShowing()).isTrue();
387     }
388 
389     @Test
onDeviceClick_audioSharingOff_setActive()390     public void onDeviceClick_audioSharingOff_setActive() {
391         mSetFlagsRule.disableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
392         when(mCachedBluetoothDevice.getDevice()).thenReturn(mDevice);
393         Pair<Drawable, String> pair = new Pair<>(mDrawable, TEST_DEVICE_NAME);
394         when(mCachedBluetoothDevice.getDrawableWithDescription()).thenReturn(pair);
395         BluetoothDevicePreference preference =
396                 new BluetoothDevicePreference(
397                         mContext,
398                         mCachedBluetoothDevice,
399                         true,
400                         BluetoothDevicePreference.SortType.TYPE_NO_SORT);
401         mAvailableMediaDeviceGroupController.onDeviceClick(preference);
402         verify(mCachedBluetoothDevice).setActive();
403     }
404 
405     @Test
onDeviceClick_audioSharingOn_dialogHandler()406     public void onDeviceClick_audioSharingOn_dialogHandler() {
407         mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
408         setUpBroadcast();
409         when(mCachedBluetoothDevice.getDevice()).thenReturn(mDevice);
410         Pair<Drawable, String> pair = new Pair<>(mDrawable, TEST_DEVICE_NAME);
411         when(mCachedBluetoothDevice.getDrawableWithDescription()).thenReturn(pair);
412         BluetoothDevicePreference preference =
413                 new BluetoothDevicePreference(
414                         mContext,
415                         mCachedBluetoothDevice,
416                         true,
417                         BluetoothDevicePreference.SortType.TYPE_NO_SORT);
418         mAvailableMediaDeviceGroupController.onDeviceClick(preference);
419         verify(mDialogHandler)
420                 .handleDeviceConnected(mCachedBluetoothDevice, /* userTriggered= */ true);
421         verify(mFeatureFactory.metricsFeatureProvider)
422                 .action(mContext, SettingsEnums.ACTION_MEDIA_DEVICE_CLICK);
423     }
424 
setUpBroadcast()425     private void setUpBroadcast() {
426         mShadowBluetoothAdapter.setIsLeAudioBroadcastSourceSupported(
427                 BluetoothStatusCodes.FEATURE_SUPPORTED);
428         mShadowBluetoothAdapter.setIsLeAudioBroadcastAssistantSupported(
429                 BluetoothStatusCodes.FEATURE_SUPPORTED);
430         when(mLocalBtProfileManager.getLeAudioBroadcastProfile()).thenReturn(mBroadcast);
431         when(mLocalBtProfileManager.getLeAudioBroadcastAssistantProfile()).thenReturn(mAssistant);
432         mAvailableMediaDeviceGroupController =
433                 spy(new AvailableMediaDeviceGroupController(mContext));
434         mAvailableMediaDeviceGroupController.setBluetoothDeviceUpdater(
435                 mAvailableMediaBluetoothDeviceUpdater);
436         mAvailableMediaDeviceGroupController.setDialogHandler(mDialogHandler);
437         mAvailableMediaDeviceGroupController.setFragmentManager(mFragManager);
438         mAvailableMediaDeviceGroupController.mPreferenceGroup = mPreferenceGroup;
439     }
440 }
441