1 /* 2 * Copyright (C) 2022 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.bluetooth; 18 19 import static android.media.Spatializer.SPATIALIZER_IMMERSIVE_LEVEL_MULTICHANNEL; 20 import static android.media.Spatializer.SPATIALIZER_IMMERSIVE_LEVEL_NONE; 21 22 import static com.google.common.truth.Truth.assertThat; 23 24 import static org.mockito.Mockito.spy; 25 import static org.mockito.Mockito.verify; 26 import static org.mockito.Mockito.when; 27 28 import android.bluetooth.BluetoothDevice; 29 import android.media.AudioDeviceAttributes; 30 import android.media.AudioDeviceInfo; 31 import android.media.AudioManager; 32 import android.media.Spatializer; 33 34 import androidx.preference.PreferenceCategory; 35 import androidx.preference.SwitchPreference; 36 37 import com.android.settingslib.core.lifecycle.Lifecycle; 38 39 import org.junit.Before; 40 import org.junit.Test; 41 import org.junit.runner.RunWith; 42 import org.mockito.Mock; 43 import org.mockito.MockitoAnnotations; 44 import org.robolectric.RobolectricTestRunner; 45 import org.robolectric.RuntimeEnvironment; 46 47 import java.util.ArrayList; 48 import java.util.List; 49 50 @RunWith(RobolectricTestRunner.class) 51 public class BluetoothDetailsSpatialAudioControllerTest extends BluetoothDetailsControllerTestBase { 52 53 private static final String MAC_ADDRESS = "04:52:C7:0B:D8:3C"; 54 private static final String KEY_SPATIAL_AUDIO = "spatial_audio"; 55 private static final String KEY_HEAD_TRACKING = "head_tracking"; 56 57 @Mock 58 private AudioManager mAudioManager; 59 @Mock 60 private Spatializer mSpatializer; 61 @Mock 62 private Lifecycle mSpatialAudioLifecycle; 63 @Mock 64 private PreferenceCategory mProfilesContainer; 65 @Mock 66 private BluetoothDevice mBluetoothDevice; 67 68 private AudioDeviceAttributes mAvailableDevice; 69 70 private BluetoothDetailsSpatialAudioController mController; 71 private SwitchPreference mSpatialAudioPref; 72 private SwitchPreference mHeadTrackingPref; 73 74 @Before setUp()75 public void setUp() { 76 MockitoAnnotations.initMocks(this); 77 78 mContext = spy(RuntimeEnvironment.application); 79 when(mContext.getSystemService(AudioManager.class)).thenReturn(mAudioManager); 80 when(mAudioManager.getSpatializer()).thenReturn(mSpatializer); 81 when(mCachedDevice.getAddress()).thenReturn(MAC_ADDRESS); 82 when(mCachedDevice.getDevice()).thenReturn(mBluetoothDevice); 83 when(mBluetoothDevice.getAnonymizedAddress()).thenReturn(MAC_ADDRESS); 84 85 mController = new BluetoothDetailsSpatialAudioController(mContext, mFragment, 86 mCachedDevice, mSpatialAudioLifecycle); 87 mController.mProfilesContainer = mProfilesContainer; 88 89 mSpatialAudioPref = mController.createSpatialAudioPreference(mContext); 90 mHeadTrackingPref = mController.createHeadTrackingPreference(mContext); 91 92 when(mProfilesContainer.findPreference(KEY_SPATIAL_AUDIO)).thenReturn(mSpatialAudioPref); 93 when(mProfilesContainer.findPreference(KEY_HEAD_TRACKING)).thenReturn(mHeadTrackingPref); 94 95 mAvailableDevice = new AudioDeviceAttributes( 96 AudioDeviceAttributes.ROLE_OUTPUT, 97 AudioDeviceInfo.TYPE_BLUETOOTH_A2DP, 98 MAC_ADDRESS); 99 } 100 101 @Test isAvailable_forSpatializerWithLevelNone_returnsFalse()102 public void isAvailable_forSpatializerWithLevelNone_returnsFalse() { 103 when(mSpatializer.getImmersiveAudioLevel()).thenReturn(SPATIALIZER_IMMERSIVE_LEVEL_NONE); 104 assertThat(mController.isAvailable()).isFalse(); 105 } 106 107 @Test isAvailable_forSpatializerWithLevelNotNone_returnsTrue()108 public void isAvailable_forSpatializerWithLevelNotNone_returnsTrue() { 109 when(mSpatializer.getImmersiveAudioLevel()).thenReturn( 110 SPATIALIZER_IMMERSIVE_LEVEL_MULTICHANNEL); 111 assertThat(mController.isAvailable()).isTrue(); 112 } 113 114 @Test refresh_spatialAudioIsTurnedOn_checksSpatialAudioPreference()115 public void refresh_spatialAudioIsTurnedOn_checksSpatialAudioPreference() { 116 List<AudioDeviceAttributes> compatibleAudioDevices = new ArrayList<>(); 117 mController.setAvailableDevice(mAvailableDevice); 118 compatibleAudioDevices.add(mController.mAudioDevice); 119 when(mSpatializer.isAvailableForDevice(mController.mAudioDevice)).thenReturn(true); 120 when(mSpatializer.getCompatibleAudioDevices()).thenReturn(compatibleAudioDevices); 121 122 mController.refresh(); 123 124 assertThat(mSpatialAudioPref.isChecked()).isTrue(); 125 } 126 127 @Test refresh_spatialAudioIsTurnedOff_unchecksSpatialAudioPreference()128 public void refresh_spatialAudioIsTurnedOff_unchecksSpatialAudioPreference() { 129 List<AudioDeviceAttributes> compatibleAudioDevices = new ArrayList<>(); 130 when(mSpatializer.getCompatibleAudioDevices()).thenReturn(compatibleAudioDevices); 131 132 mController.refresh(); 133 134 assertThat(mSpatialAudioPref.isChecked()).isFalse(); 135 } 136 137 @Test refresh_spatialAudioOnAndHeadTrackingIsAvailable_showsHeadTrackingPreference()138 public void refresh_spatialAudioOnAndHeadTrackingIsAvailable_showsHeadTrackingPreference() { 139 List<AudioDeviceAttributes> compatibleAudioDevices = new ArrayList<>(); 140 compatibleAudioDevices.add(mController.mAudioDevice); 141 when(mSpatializer.getCompatibleAudioDevices()).thenReturn(compatibleAudioDevices); 142 when(mSpatializer.hasHeadTracker(mController.mAudioDevice)).thenReturn(true); 143 144 mController.refresh(); 145 146 assertThat(mHeadTrackingPref.isVisible()).isTrue(); 147 } 148 149 @Test 150 public void refresh_spatialAudioOnAndHeadTrackingIsNotAvailable_hidesHeadTrackingPreference()151 refresh_spatialAudioOnAndHeadTrackingIsNotAvailable_hidesHeadTrackingPreference() { 152 List<AudioDeviceAttributes> compatibleAudioDevices = new ArrayList<>(); 153 mController.setAvailableDevice(mAvailableDevice); 154 compatibleAudioDevices.add(mController.mAudioDevice); 155 when(mSpatializer.getCompatibleAudioDevices()).thenReturn(compatibleAudioDevices); 156 when(mSpatializer.hasHeadTracker(mController.mAudioDevice)).thenReturn(false); 157 158 mController.refresh(); 159 160 verify(mProfilesContainer).removePreference(mHeadTrackingPref); 161 } 162 163 @Test refresh_spatialAudioOff_hidesHeadTrackingPreference()164 public void refresh_spatialAudioOff_hidesHeadTrackingPreference() { 165 List<AudioDeviceAttributes> compatibleAudioDevices = new ArrayList<>(); 166 when(mSpatializer.getCompatibleAudioDevices()).thenReturn(compatibleAudioDevices); 167 168 mController.refresh(); 169 170 verify(mProfilesContainer).removePreference(mHeadTrackingPref); 171 } 172 173 @Test refresh_headTrackingIsTurnedOn_checksHeadTrackingPreference()174 public void refresh_headTrackingIsTurnedOn_checksHeadTrackingPreference() { 175 List<AudioDeviceAttributes> compatibleAudioDevices = new ArrayList<>(); 176 mController.setAvailableDevice(mAvailableDevice); 177 compatibleAudioDevices.add(mController.mAudioDevice); 178 when(mSpatializer.getCompatibleAudioDevices()).thenReturn(compatibleAudioDevices); 179 when(mSpatializer.isAvailableForDevice(mController.mAudioDevice)).thenReturn(true); 180 when(mSpatializer.hasHeadTracker(mController.mAudioDevice)).thenReturn(true); 181 when(mSpatializer.isHeadTrackerEnabled(mController.mAudioDevice)).thenReturn(true); 182 183 mController.refresh(); 184 185 assertThat(mHeadTrackingPref.isChecked()).isTrue(); 186 } 187 188 @Test refresh_headTrackingIsTurnedOff_unchecksHeadTrackingPreference()189 public void refresh_headTrackingIsTurnedOff_unchecksHeadTrackingPreference() { 190 List<AudioDeviceAttributes> compatibleAudioDevices = new ArrayList<>(); 191 mController.setAvailableDevice(mAvailableDevice); 192 compatibleAudioDevices.add(mController.mAudioDevice); 193 when(mSpatializer.getCompatibleAudioDevices()).thenReturn(compatibleAudioDevices); 194 when(mSpatializer.isAvailableForDevice(mController.mAudioDevice)).thenReturn(true); 195 when(mSpatializer.hasHeadTracker(mController.mAudioDevice)).thenReturn(true); 196 when(mSpatializer.isHeadTrackerEnabled(mController.mAudioDevice)).thenReturn(false); 197 198 mController.refresh(); 199 200 assertThat(mHeadTrackingPref.isChecked()).isFalse(); 201 } 202 203 @Test turnedOnSpatialAudio_invokesAddCompatibleAudioDevice()204 public void turnedOnSpatialAudio_invokesAddCompatibleAudioDevice() { 205 mController.setAvailableDevice(mAvailableDevice); 206 mSpatialAudioPref.setChecked(true); 207 mController.onPreferenceClick(mSpatialAudioPref); 208 verify(mSpatializer).addCompatibleAudioDevice(mController.mAudioDevice); 209 } 210 211 @Test turnedOffSpatialAudio_invokesRemoveCompatibleAudioDevice()212 public void turnedOffSpatialAudio_invokesRemoveCompatibleAudioDevice() { 213 mController.setAvailableDevice(mAvailableDevice); 214 mSpatialAudioPref.setChecked(false); 215 mController.onPreferenceClick(mSpatialAudioPref); 216 verify(mSpatializer).removeCompatibleAudioDevice(mController.mAudioDevice); 217 } 218 219 @Test turnedOnHeadTracking_invokesSetHeadTrackerEnabled_setsTrue()220 public void turnedOnHeadTracking_invokesSetHeadTrackerEnabled_setsTrue() { 221 mController.setAvailableDevice(mAvailableDevice); 222 mHeadTrackingPref.setChecked(true); 223 mController.onPreferenceClick(mHeadTrackingPref); 224 verify(mSpatializer).setHeadTrackerEnabled(true, mController.mAudioDevice); 225 } 226 227 @Test turnedOffHeadTracking_invokesSetHeadTrackerEnabled_setsFalse()228 public void turnedOffHeadTracking_invokesSetHeadTrackerEnabled_setsFalse() { 229 mController.setAvailableDevice(mAvailableDevice); 230 mHeadTrackingPref.setChecked(false); 231 mController.onPreferenceClick(mHeadTrackingPref); 232 verify(mSpatializer).setHeadTrackerEnabled(false, mController.mAudioDevice); 233 } 234 } 235