• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 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 
17 package com.android.settings.sound;
18 
19 import static android.media.AudioSystem.DEVICE_OUT_BLE_HEADSET;
20 import static android.media.AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP;
21 import static android.media.AudioSystem.DEVICE_OUT_EARPIECE;
22 import static android.media.AudioSystem.DEVICE_OUT_HEARING_AID;
23 
24 import static com.google.common.truth.Truth.assertThat;
25 
26 import static org.mockito.ArgumentMatchers.any;
27 import static org.mockito.Mockito.doReturn;
28 import static org.mockito.Mockito.mock;
29 import static org.mockito.Mockito.never;
30 import static org.mockito.Mockito.spy;
31 import static org.mockito.Mockito.verify;
32 import static org.mockito.Mockito.when;
33 
34 import android.bluetooth.BluetoothAdapter;
35 import android.bluetooth.BluetoothDevice;
36 import android.bluetooth.BluetoothManager;
37 import android.content.Context;
38 import android.content.Intent;
39 import android.content.pm.ApplicationInfo;
40 import android.content.pm.PackageInfo;
41 import android.content.pm.PackageStats;
42 import android.media.AudioAttributes;
43 import android.media.AudioManager;
44 import android.media.VolumeProvider;
45 import android.media.session.MediaController;
46 import android.media.session.MediaSessionManager;
47 import android.media.session.PlaybackState;
48 
49 import androidx.preference.Preference;
50 import androidx.preference.PreferenceManager;
51 import androidx.preference.PreferenceScreen;
52 
53 import com.android.settings.R;
54 import com.android.settings.bluetooth.Utils;
55 import com.android.settings.testutils.shadow.ShadowAudioManager;
56 import com.android.settings.testutils.shadow.ShadowBluetoothUtils;
57 import com.android.settingslib.bluetooth.A2dpProfile;
58 import com.android.settingslib.bluetooth.BluetoothEventManager;
59 import com.android.settingslib.bluetooth.CachedBluetoothDevice;
60 import com.android.settingslib.bluetooth.CachedBluetoothDeviceManager;
61 import com.android.settingslib.bluetooth.HearingAidProfile;
62 import com.android.settingslib.bluetooth.LeAudioProfile;
63 import com.android.settingslib.bluetooth.LocalBluetoothManager;
64 import com.android.settingslib.bluetooth.LocalBluetoothProfileManager;
65 import com.android.settingslib.media.MediaOutputConstants;
66 
67 import org.junit.After;
68 import org.junit.Before;
69 import org.junit.Test;
70 import org.junit.runner.RunWith;
71 import org.mockito.ArgumentCaptor;
72 import org.mockito.Mock;
73 import org.mockito.MockitoAnnotations;
74 import org.robolectric.RobolectricTestRunner;
75 import org.robolectric.RuntimeEnvironment;
76 import org.robolectric.Shadows;
77 import org.robolectric.annotation.Config;
78 import org.robolectric.shadows.ShadowBluetoothDevice;
79 import org.robolectric.shadows.ShadowPackageManager;
80 
81 import java.util.ArrayList;
82 import java.util.Collection;
83 import java.util.List;
84 
85 @RunWith(RobolectricTestRunner.class)
86 @Config(shadows = {
87         ShadowAudioManager.class,
88         ShadowBluetoothUtils.class,
89         ShadowBluetoothDevice.class}
90 )
91 public class MediaOutputPreferenceControllerTest {
92     private static final String TEST_KEY = "Test_Key";
93     private static final String TEST_DEVICE_NAME_1 = "Test_A2DP_BT_Device_NAME_1";
94     private static final String TEST_DEVICE_NAME_2 = "Test_A2DP_BT_Device_NAME_2";
95     private static final String TEST_HAP_DEVICE_NAME_1 = "Test_HAP_BT_Device_NAME_1";
96     private static final String TEST_HAP_DEVICE_NAME_2 = "Test_HAP_BT_Device_NAME_2";
97     private static final String TEST_LE_AUDIO_DEVICE_NAME_1 = "Test_LE_AUDIO_Device_NAME_1";
98     private static final String TEST_DEVICE_ADDRESS_1 = "00:A1:A1:A1:A1:A1";
99     private static final String TEST_DEVICE_ADDRESS_2 = "00:B2:B2:B2:B2:B2";
100     private static final String TEST_DEVICE_ADDRESS_3 = "00:C3:C3:C3:C3:C3";
101     private static final String TEST_DEVICE_ADDRESS_4 = "00:D4:D4:D4:D4:D4";
102     private static final String TEST_DEVICE_ADDRESS_5 = "00:E5:E5:E5:E5:E5";
103     private static final String TEST_PACKAGE_NAME = "com.test.packagename";
104     private static final String TEST_APPLICATION_LABEL = "APP Test Label";
105 
106     @Mock
107     private LocalBluetoothManager mLocalManager;
108     @Mock
109     private BluetoothEventManager mBluetoothEventManager;
110     @Mock
111     private LocalBluetoothProfileManager mLocalBluetoothProfileManager;
112     @Mock
113     private A2dpProfile mA2dpProfile;
114     @Mock
115     private HearingAidProfile mHearingAidProfile;
116     @Mock
117     private LeAudioProfile mLeAudioProfile;
118     @Mock
119     private AudioSwitchPreferenceController.AudioSwitchCallback mAudioSwitchPreferenceCallback;
120     @Mock
121     private MediaSessionManager mMediaSessionManager;
122     @Mock
123     private MediaController mMediaController;
124     @Mock
125     private CachedBluetoothDeviceManager mCachedDeviceManager;
126     @Mock
127     private CachedBluetoothDevice mCachedBluetoothDeviceL;
128     @Mock
129     private CachedBluetoothDevice mCachedBluetoothDeviceR;
130 
131     private Context mContext;
132     private PreferenceScreen mScreen;
133     private Preference mPreference;
134     private AudioManager mAudioManager;
135     private ShadowAudioManager mShadowAudioManager;
136     private BluetoothManager mBluetoothManager;
137     private BluetoothAdapter mBluetoothAdapter;
138     private BluetoothDevice mBluetoothDevice;
139     private BluetoothDevice mSecondBluetoothDevice;
140     private BluetoothDevice mLeftBluetoothHapDevice;
141     private BluetoothDevice mRightBluetoothHapDevice;
142     private LocalBluetoothManager mLocalBluetoothManager;
143     private MediaOutputPreferenceController mController;
144     private List<BluetoothDevice> mProfileConnectedDevices;
145     private List<BluetoothDevice> mHearingAidActiveDevices;
146     private List<BluetoothDevice> mLeAudioActiveDevices;
147     private List<MediaController> mMediaControllers = new ArrayList<>();
148     private MediaController.PlaybackInfo mPlaybackInfo;
149     private PlaybackState mPlaybackState;
150     private ShadowPackageManager mShadowPackageManager;
151     private ApplicationInfo mAppInfo;
152     private PackageInfo mPackageInfo;
153     private PackageStats mPackageStats;
154     private Collection<CachedBluetoothDevice> mCachedDevices;
155 
156     @Before
setUp()157     public void setUp() {
158         MockitoAnnotations.initMocks(this);
159         mContext = spy(RuntimeEnvironment.application);
160 
161         mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
162         mShadowAudioManager = ShadowAudioManager.getShadow();
163 
164         ShadowBluetoothUtils.sLocalBluetoothManager = mLocalManager;
165         mLocalBluetoothManager = Utils.getLocalBtManager(mContext);
166 
167         doReturn(mMediaSessionManager).when(mContext).getSystemService(MediaSessionManager.class);
168         when(mMediaSessionManager.getActiveSessions(any())).thenReturn(mMediaControllers);
169         when(mMediaController.getPackageName()).thenReturn(TEST_PACKAGE_NAME);
170         mPlaybackInfo = new MediaController.PlaybackInfo(
171                 MediaController.PlaybackInfo.PLAYBACK_TYPE_LOCAL,
172                 VolumeProvider.VOLUME_CONTROL_ABSOLUTE,
173                 100,
174                 10,
175                 new AudioAttributes.Builder().setUsage(AudioAttributes.USAGE_MEDIA).build(),
176                 null);
177         mPlaybackState = new PlaybackState.Builder()
178                 .setState(PlaybackState.STATE_PLAYING, 0, 1)
179                 .build();
180         when(mMediaController.getPlaybackInfo()).thenReturn(mPlaybackInfo);
181         when(mMediaController.getPlaybackState()).thenReturn(mPlaybackState);
182         mMediaControllers.add(mMediaController);
183 
184         when(mLocalBluetoothManager.getEventManager()).thenReturn(mBluetoothEventManager);
185         when(mLocalBluetoothManager.getProfileManager()).thenReturn(mLocalBluetoothProfileManager);
186         when(mLocalBluetoothManager.getCachedDeviceManager()).thenReturn(mCachedDeviceManager);
187         when(mLocalBluetoothProfileManager.getA2dpProfile()).thenReturn(mA2dpProfile);
188         when(mLocalBluetoothProfileManager.getHearingAidProfile()).thenReturn(mHearingAidProfile);
189         when(mLocalBluetoothProfileManager.getLeAudioProfile()).thenReturn(mLeAudioProfile);
190 
191         mBluetoothManager = mContext.getSystemService(BluetoothManager.class);
192         mBluetoothAdapter = mBluetoothManager.getAdapter();
193 
194         mCachedDevices = new ArrayList<>();
195         mCachedDevices.add(mCachedBluetoothDeviceL);
196         mCachedDevices.add(mCachedBluetoothDeviceR);
197         when(mCachedDeviceManager.getCachedDevicesCopy()).thenReturn(mCachedDevices);
198 
199         mBluetoothDevice = spy(mBluetoothAdapter.getRemoteDevice(TEST_DEVICE_ADDRESS_1));
200         when(mBluetoothDevice.getName()).thenReturn(TEST_DEVICE_NAME_1);
201         when(mBluetoothDevice.getAlias()).thenReturn(TEST_DEVICE_NAME_1);
202         when(mBluetoothDevice.isConnected()).thenReturn(true);
203 
204         mSecondBluetoothDevice = spy(mBluetoothAdapter.getRemoteDevice(TEST_DEVICE_ADDRESS_2));
205         when(mSecondBluetoothDevice.getName()).thenReturn(TEST_DEVICE_NAME_2);
206         when(mSecondBluetoothDevice.isConnected()).thenReturn(true);
207 
208         mLeftBluetoothHapDevice = spy(mBluetoothAdapter.getRemoteDevice(TEST_DEVICE_ADDRESS_3));
209         when(mLeftBluetoothHapDevice.getName()).thenReturn(TEST_HAP_DEVICE_NAME_1);
210         when(mLeftBluetoothHapDevice.getAlias()).thenReturn(TEST_HAP_DEVICE_NAME_1);
211         when(mLeftBluetoothHapDevice.isConnected()).thenReturn(true);
212 
213         mRightBluetoothHapDevice = spy(mBluetoothAdapter.getRemoteDevice(TEST_DEVICE_ADDRESS_4));
214         when(mRightBluetoothHapDevice.getName()).thenReturn(TEST_HAP_DEVICE_NAME_2);
215         when(mRightBluetoothHapDevice.isConnected()).thenReturn(true);
216 
217         mController = new MediaOutputPreferenceController(mContext, TEST_KEY);
218         mScreen = spy(new PreferenceScreen(mContext, null));
219         mPreference = new Preference(mContext);
220         mProfileConnectedDevices = new ArrayList<>();
221         mHearingAidActiveDevices = new ArrayList<>(2);
222         mLeAudioActiveDevices = new ArrayList<>();
223 
224         when(mScreen.getPreferenceManager()).thenReturn(mock(PreferenceManager.class));
225         when(mScreen.getContext()).thenReturn(mContext);
226         when(mScreen.findPreference(mController.getPreferenceKey())).thenReturn(mPreference);
227         mScreen.addPreference(mPreference);
228         mController.displayPreference(mScreen);
229         mController.setCallback(mAudioSwitchPreferenceCallback);
230     }
231 
232     @After
tearDown()233     public void tearDown() {
234         ShadowBluetoothUtils.reset();
235     }
236 
237     /**
238      * A2DP Bluetooth device(s) are connected, but no device is set as activated
239      * Preference summary should be "This device"
240      */
241     @Test
updateState_withConnectedBtDevice_withoutActiveBtDevice_setDefaultSummary()242     public void updateState_withConnectedBtDevice_withoutActiveBtDevice_setDefaultSummary() {
243         mShadowAudioManager.setOutputDevice(DEVICE_OUT_EARPIECE);
244         mAudioManager.setMode(AudioManager.MODE_NORMAL);
245         mProfileConnectedDevices.clear();
246         mProfileConnectedDevices.add(mBluetoothDevice);
247         mProfileConnectedDevices.add(mSecondBluetoothDevice);
248         when(mA2dpProfile.getConnectedDevices()).thenReturn(mProfileConnectedDevices);
249         when(mA2dpProfile.getActiveDevice()).thenReturn(null);
250 
251         assertThat(mPreference.getSummary()).isNull();
252         mController.updateState(mPreference);
253         assertThat(mPreference.getSummary()).isEqualTo(
254                 mContext.getText(R.string.media_output_default_summary));
255     }
256 
257     /**
258      * A2DP Bluetooth device(s) are connected and active
259      * Preference summary should be device's name
260      */
261     @Test
updateState_withActiveBtDevice_setActivatedDeviceName()262     public void updateState_withActiveBtDevice_setActivatedDeviceName() {
263         mShadowAudioManager.setOutputDevice(DEVICE_OUT_BLUETOOTH_A2DP);
264         mAudioManager.setMode(AudioManager.MODE_NORMAL);
265         mProfileConnectedDevices.clear();
266         mProfileConnectedDevices.add(mBluetoothDevice);
267         mProfileConnectedDevices.add(mSecondBluetoothDevice);
268         when(mA2dpProfile.getConnectedDevices()).thenReturn(mProfileConnectedDevices);
269         when(mA2dpProfile.getActiveDevice()).thenReturn(mBluetoothDevice);
270 
271         assertThat(mPreference.getSummary()).isNull();
272         mController.updateState(mPreference);
273         assertThat(mPreference.getSummary()).isEqualTo(TEST_DEVICE_NAME_1);
274     }
275 
276     /**
277      * Hearing Aid device(s) are connected and active
278      * Preference summary should be device's name
279      */
280     @Test
updateState_withActiveHADevice_setActivatedDeviceName()281     public void updateState_withActiveHADevice_setActivatedDeviceName() {
282         mShadowAudioManager.setOutputDevice(DEVICE_OUT_HEARING_AID);
283         mAudioManager.setMode(AudioManager.MODE_NORMAL);
284         mHearingAidActiveDevices.clear();
285         mHearingAidActiveDevices.add(mLeftBluetoothHapDevice);
286         when(mHearingAidProfile.getConnectedDevices()).thenReturn(mHearingAidActiveDevices);
287         when(mHearingAidProfile.getActiveDevices()).thenReturn(mHearingAidActiveDevices);
288 
289         assertThat(mPreference.getSummary()).isNull();
290         mController.updateState(mPreference);
291         assertThat(mPreference.getSummary()).isEqualTo(TEST_HAP_DEVICE_NAME_1);
292 
293     }
294 
295     @Test
updateState_withActiveLeAudioDevice_setActivatedDeviceName()296     public void updateState_withActiveLeAudioDevice_setActivatedDeviceName() {
297         mShadowAudioManager.setOutputDevice(DEVICE_OUT_BLE_HEADSET);
298         mAudioManager.setMode(AudioManager.MODE_NORMAL);
299         when(mCachedBluetoothDeviceL.getDevice()).thenReturn(mBluetoothDevice);
300         when(mCachedBluetoothDeviceR.getDevice()).thenReturn(mSecondBluetoothDevice);
301         when(mBluetoothDevice.getAlias()).thenReturn(TEST_LE_AUDIO_DEVICE_NAME_1);
302         mProfileConnectedDevices.clear();
303         mProfileConnectedDevices.add(mBluetoothDevice);
304         mProfileConnectedDevices.add(mSecondBluetoothDevice);
305         mLeAudioActiveDevices.clear();
306         mLeAudioActiveDevices.add(mBluetoothDevice);
307         when(mLeAudioProfile.getConnectedDevices()).thenReturn(mProfileConnectedDevices);
308         when(mLeAudioProfile.getActiveDevices()).thenReturn(mLeAudioActiveDevices);
309 
310         assertThat(mPreference.getSummary()).isNull();
311         mController.updateState(mPreference);
312         assertThat(mPreference.getSummary()).isEqualTo(TEST_LE_AUDIO_DEVICE_NAME_1);
313     }
314 
315     @Test
updateState_noActiveLocalPlayback_noTitle()316     public void updateState_noActiveLocalPlayback_noTitle() {
317         mPlaybackState = new PlaybackState.Builder()
318                 .setState(PlaybackState.STATE_NONE, 0, 1)
319                 .build();
320         when(mMediaController.getPlaybackState()).thenReturn(mPlaybackState);
321         mController = new MediaOutputPreferenceController(mContext, TEST_KEY);
322 
323         mController.updateState(mPreference);
324 
325         assertThat(mPreference.getTitle()).isNull();
326     }
327 
328     @Test
updateState_withActiveLocalPlayback_checkTitle()329     public void updateState_withActiveLocalPlayback_checkTitle() {
330         initPackage();
331         mShadowPackageManager.addPackage(mPackageInfo, mPackageStats);
332 
333         mController.updateState(mPreference);
334 
335         assertThat(mPreference.getTitle()).isEqualTo(
336                 mContext.getString(R.string.media_output_label_title, TEST_APPLICATION_LABEL));
337     }
338 
339     @Test
click_launch_outputSwitcherSlice()340     public void click_launch_outputSwitcherSlice() {
341         final ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
342         mController.handlePreferenceTreeClick(mPreference);
343         verify(mContext, never()).startActivity(intentCaptor.capture());
344 
345         mPreference.setKey(TEST_KEY);
346         mController.handlePreferenceTreeClick(mPreference);
347         verify(mContext).sendBroadcast(intentCaptor.capture());
348         assertThat(intentCaptor.getValue().getAction())
349                 .isEqualTo(MediaOutputConstants.ACTION_LAUNCH_MEDIA_OUTPUT_DIALOG);
350     }
351 
352     /**
353      * Default status
354      * Preference should be invisible
355      * Summary should be default summary
356      */
357     @Test
updateState_notInCall_preferenceVisible()358     public void updateState_notInCall_preferenceVisible() {
359         mAudioManager.setMode(AudioManager.MODE_NORMAL);
360 
361         mController.updateState(mPreference);
362 
363         assertThat(mPreference.isVisible()).isTrue();
364     }
365 
366     /**
367      * During a call
368      * Preference should be invisible
369      * Default string should be "Unavailable during calls"
370      */
371     @Test
updateState_inCall_preferenceInvisible()372     public void updateState_inCall_preferenceInvisible() {
373         mAudioManager.setMode(AudioManager.MODE_IN_COMMUNICATION);
374 
375         mController.updateState(mPreference);
376 
377         assertThat(mPreference.isVisible()).isFalse();
378     }
379 
380     @Test
findActiveDevice_onlyA2dpDeviceActive_returnA2dpDevice()381     public void findActiveDevice_onlyA2dpDeviceActive_returnA2dpDevice() {
382         when(mLocalBluetoothProfileManager.getHearingAidProfile()).thenReturn(null);
383         when(mA2dpProfile.getActiveDevice()).thenReturn(mBluetoothDevice);
384 
385         assertThat(mController.findActiveDevice()).isEqualTo(mBluetoothDevice);
386     }
387 
388     @Test
findActiveDevice_allDevicesNotActive_returnNull()389     public void findActiveDevice_allDevicesNotActive_returnNull() {
390         when(mLocalBluetoothProfileManager.getHearingAidProfile()).thenReturn(null);
391         when(mA2dpProfile.getActiveDevice()).thenReturn(null);
392 
393         assertThat(mController.findActiveDevice()).isNull();
394     }
395 
396     @Test
findActiveDevice_allProfilesWithActiveDevice_returnHADevice()397     public void findActiveDevice_allProfilesWithActiveDevice_returnHADevice() {
398         BluetoothDevice btLeDevice = spy(mBluetoothAdapter.getRemoteDevice(TEST_DEVICE_ADDRESS_5));
399         when(btLeDevice.getName()).thenReturn(TEST_LE_AUDIO_DEVICE_NAME_1);
400         mLeAudioActiveDevices.clear();
401         mLeAudioActiveDevices.add(btLeDevice);
402         mHearingAidActiveDevices.clear();
403         mHearingAidActiveDevices.add(mLeftBluetoothHapDevice);
404         when(mHearingAidProfile.getActiveDevices()).thenReturn(mHearingAidActiveDevices);
405         when(mA2dpProfile.getActiveDevice()).thenReturn(mBluetoothDevice);
406         when(mLeAudioProfile.getActiveDevices()).thenReturn(mLeAudioActiveDevices);
407 
408         assertThat(mController.findActiveDevice()).isEqualTo(mLeftBluetoothHapDevice);
409     }
410 
411     @Test
findActiveDevice_a2dpDeviceAndLeAudioDeviceActive_returnLeAudioDevice()412     public void findActiveDevice_a2dpDeviceAndLeAudioDeviceActive_returnLeAudioDevice() {
413         BluetoothDevice btLeDevice = spy(mBluetoothAdapter.getRemoteDevice(TEST_DEVICE_ADDRESS_5));
414         when(btLeDevice.getName()).thenReturn(TEST_LE_AUDIO_DEVICE_NAME_1);
415         mLeAudioActiveDevices.clear();
416         mLeAudioActiveDevices.add(btLeDevice);
417         mHearingAidActiveDevices.clear();
418         when(mHearingAidProfile.getActiveDevices()).thenReturn(mHearingAidActiveDevices);
419         when(mA2dpProfile.getActiveDevice()).thenReturn(mBluetoothDevice);
420         when(mLeAudioProfile.getActiveDevices()).thenReturn(mLeAudioActiveDevices);
421 
422         assertThat(mController.findActiveDevice()).isEqualTo(btLeDevice);
423     }
424 
425     @Test
findActiveDevice_onlyLeAudioDeviceActive_returnLeAudioDevice()426     public void findActiveDevice_onlyLeAudioDeviceActive_returnLeAudioDevice() {
427         BluetoothDevice btLeDevice = spy(mBluetoothAdapter.getRemoteDevice(TEST_DEVICE_ADDRESS_5));
428         when(btLeDevice.getName()).thenReturn(TEST_LE_AUDIO_DEVICE_NAME_1);
429         mLeAudioActiveDevices.clear();
430         mLeAudioActiveDevices.add(btLeDevice);
431         mHearingAidActiveDevices.clear();
432         when(mHearingAidProfile.getActiveDevices()).thenReturn(mHearingAidActiveDevices);
433         when(mA2dpProfile.getActiveDevice()).thenReturn(null);
434         when(mLeAudioProfile.getActiveDevices()).thenReturn(mLeAudioActiveDevices);
435 
436         assertThat(mController.findActiveDevice()).isEqualTo(btLeDevice);
437     }
438 
initPackage()439     private void initPackage() {
440         mShadowPackageManager = Shadows.shadowOf(mContext.getPackageManager());
441         mAppInfo = new ApplicationInfo();
442         mAppInfo.flags = ApplicationInfo.FLAG_INSTALLED;
443         mAppInfo.packageName = TEST_PACKAGE_NAME;
444         mAppInfo.name = TEST_APPLICATION_LABEL;
445         mPackageInfo = new PackageInfo();
446         mPackageInfo.packageName = TEST_PACKAGE_NAME;
447         mPackageInfo.applicationInfo = mAppInfo;
448         mPackageStats = new PackageStats(TEST_PACKAGE_NAME);
449     }
450 }
451