1 /*
2  * Copyright 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 package com.android.server.audio;
17 
18 import static org.mockito.ArgumentMatchers.any;
19 import static org.mockito.ArgumentMatchers.anyBoolean;
20 import static org.mockito.Mockito.doNothing;
21 import static org.mockito.Mockito.mock;
22 import static org.mockito.Mockito.spy;
23 import static org.mockito.Mockito.when;
24 
25 import android.media.AudioAttributes;
26 import android.media.AudioDeviceAttributes;
27 import android.media.AudioFormat;
28 import android.media.AudioSystem;
29 import android.util.Log;
30 
31 import androidx.test.filters.MediumTest;
32 import androidx.test.platform.app.InstrumentationRegistry;
33 import androidx.test.runner.AndroidJUnit4;
34 
35 import org.junit.Assert;
36 import org.junit.Before;
37 import org.junit.Test;
38 import org.junit.runner.RunWith;
39 import org.mockito.Mock;
40 import org.mockito.Spy;
41 
42 import java.util.ArrayList;
43 import java.util.List;
44 
45 @MediumTest
46 @RunWith(AndroidJUnit4.class)
47 public class SpatializerHelperTest {
48 
49     private static final String TAG = "SpatializerHelperTest";
50 
51     // the actual class under test
52     private SpatializerHelper mSpatHelper;
53 
54     @Mock private AudioService mMockAudioService;
55     @Spy private AudioSystemAdapter mSpyAudioSystem;
56     @Spy private AudioDeviceBroker mSpyDeviceBroker;
57 
58     @Before
setUp()59     public void setUp() throws Exception {
60         mMockAudioService = mock(AudioService.class);
61 
62         mSpyAudioSystem = spy(new NoOpAudioSystemAdapter());
63         mSpyDeviceBroker = spy(
64                 new AudioDeviceBroker(
65                         InstrumentationRegistry.getInstrumentation().getTargetContext(),
66                         mMockAudioService, mSpyAudioSystem));
67         mSpatHelper = new SpatializerHelper(mMockAudioService, mSpyAudioSystem,
68                 mSpyDeviceBroker, /*binauralEnabledDefault=*/true, /*transauralEnabledDefault=*/
69                 true, /*headTrackingEnabledDefault*/false);
70     }
71 
72     @Test
testAdiDeviceStateSettings()73     public void testAdiDeviceStateSettings() throws Exception {
74         Log.i(TAG, "starting testSADeviceSettings");
75         final AudioDeviceAttributes dev1 =
76                 new AudioDeviceAttributes(AudioSystem.DEVICE_OUT_SPEAKER, "");
77         final AudioDeviceAttributes dev2 =
78                 new AudioDeviceAttributes(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, "C3:PO:beep");
79         final AudioDeviceAttributes dev3 =
80                 new AudioDeviceAttributes(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, "R2:D2:bloop");
81 
82         doNothing().when(mSpyDeviceBroker).persistAudioDeviceSettings();
83         mSpatHelper.initForTest(true /*binaural*/, true /*transaural*/);
84 
85         // test with single device
86         mSpatHelper.addCompatibleAudioDevice(dev1);
87         checkAddSettings();
88 
89         // test with 2+ devices so separator character is used in list
90         mSpatHelper.addCompatibleAudioDevice(dev2);
91         Assert.assertTrue(mSpatHelper.isAvailableForDevice(dev2));
92         checkAddSettings();
93         Assert.assertTrue(mSpatHelper.isAvailableForDevice(dev2));
94         mSpatHelper.addCompatibleAudioDevice(dev3);
95         checkAddSettings();
96 
97         // test adding a device twice in the list
98         mSpatHelper.addCompatibleAudioDevice(dev1);
99         checkAddSettings();
100 
101         // test removing a device
102         mSpatHelper.removeCompatibleAudioDevice(dev2);
103         // spatializer could still be run for dev2 (is available) but spatial audio
104         // is disabled for dev2 by removeCompatibleAudioDevice
105         Assert.assertTrue(mSpatHelper.isAvailableForDevice(dev2));
106         List<AudioDeviceAttributes> compatDevices = mSpatHelper.getCompatibleAudioDevices();
107         Assert.assertFalse(compatDevices.stream().anyMatch(dev -> dev.equalTypeAddress(dev2)));
108         checkAddSettings();
109     }
110 
111     /**
112      * Gets the string representing the current configuration of the devices, then clears it
113      * and restores the configuration. Verify the new string from the restored settings matches
114      * the original one.
115      */
checkAddSettings()116     private void checkAddSettings() throws Exception {
117         String settings = mSpyDeviceBroker.getDeviceSettings();
118         Log.i(TAG, "device settings: " + settings);
119         mSpyDeviceBroker.clearDeviceInventory();
120         mSpyDeviceBroker.setDeviceSettings(settings);
121         String settingsRestored = mSpyDeviceBroker.getDeviceSettings();
122         Log.i(TAG, "device settingsRestored: " + settingsRestored);
123         Assert.assertEquals(settings, settingsRestored);
124     }
125 
126     /**
127      * Test that null devices for routing do not break canBeSpatialized
128      * @throws Exception
129      */
130     @Test
testNoRoutingCanBeSpatialized()131     public void testNoRoutingCanBeSpatialized() throws Exception {
132         Log.i(TAG, "Starting testNoRoutingCanBeSpatialized");
133         mSpatHelper.forceStateForTest(SpatializerHelper.STATE_ENABLED_AVAILABLE);
134 
135         final ArrayList<AudioDeviceAttributes> emptyList = new ArrayList<>(0);
136         final ArrayList<AudioDeviceAttributes> listWithNull = new ArrayList<>(1);
137         listWithNull.add(null);
138         final AudioAttributes media = new AudioAttributes.Builder()
139                 .setUsage(AudioAttributes.USAGE_MEDIA).build();
140         final AudioFormat spatialFormat = new AudioFormat.Builder()
141                 .setEncoding(AudioFormat.ENCODING_PCM_16BIT)
142                 .setChannelMask(AudioFormat.CHANNEL_OUT_5POINT1).build();
143 
144         when(mSpyAudioSystem.getDevicesForAttributes(any(AudioAttributes.class), anyBoolean()))
145                 .thenReturn(emptyList);
146         Assert.assertFalse("can be spatialized on empty routing",
147                 mSpatHelper.canBeSpatialized(media, spatialFormat));
148 
149         when(mSpyAudioSystem.getDevicesForAttributes(any(AudioAttributes.class), anyBoolean()))
150                 .thenReturn(listWithNull);
151         Assert.assertFalse("can be spatialized on null routing",
152                 mSpatHelper.canBeSpatialized(media, spatialFormat));
153     }
154 }
155