1 /* 2 * Copyright (C) 2023 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 android.media.audio.cts; 18 19 import static org.junit.Assert.assertEquals; 20 21 import android.content.Context; 22 import android.content.pm.PackageManager; 23 import android.media.AudioAttributes; 24 import android.media.AudioDeviceInfo; 25 import android.media.AudioManager; 26 import android.media.AudioRecordingConfiguration; 27 import android.media.audiopolicy.AudioProductStrategy; 28 import android.os.PowerManager; 29 30 import androidx.test.InstrumentationRegistry; 31 32 import java.util.ArrayList; 33 import java.util.List; 34 import java.util.concurrent.CountDownLatch; 35 import java.util.concurrent.TimeUnit; 36 import java.util.function.IntSupplier; 37 38 class AudioTestUtil { 39 // Default matches the invalid (empty) attributes from native. 40 // The difference is the input source default which is not aligned between native and java 41 public static final AudioAttributes DEFAULT_ATTRIBUTES = 42 AudioProductStrategy.getDefaultAttributes(); 43 public static final AudioAttributes INVALID_ATTRIBUTES = new AudioAttributes.Builder().build(); 44 45 // Basic Device Attributes hasAudioOutput(Context context)46 public static boolean hasAudioOutput(Context context) { 47 return context.getPackageManager().hasSystemFeature( 48 PackageManager.FEATURE_AUDIO_OUTPUT); 49 } 50 hasAudioInput(Context context)51 public static boolean hasAudioInput(Context context) { 52 return context.getPackageManager().hasSystemFeature( 53 PackageManager.FEATURE_MICROPHONE); 54 } 55 resetVolumeIndex(int indexMin, int indexMax)56 public static int resetVolumeIndex(int indexMin, int indexMax) { 57 return (indexMax + indexMin) / 2; 58 } 59 incrementVolumeIndex(int index, int indexMin, int indexMax)60 public static int incrementVolumeIndex(int index, int indexMin, int indexMax) { 61 return (index + 1 > indexMax) ? resetVolumeIndex(indexMin, indexMax) : ++index; 62 } 63 64 //----------------------------------------------------------------------------------- 65 66 /** 67 * A test helper class to help compare an expected int against the result of an IntSupplier 68 * lambda. It supports specifying a max wait time, broken down into Thread.sleep() of the 69 * given period. The expected value is compared against the result of the lambda every period. 70 * It will assert if the expected value is never returned after the maximum specified time. 71 * Example of how to use: 72 * <pre> 73 * final SleepAssertIntEquals test = new SleepAssertIntEquals( 74 * 5000, // max sleep duration is 5s 75 * 100, // test condition will be checked every 100ms 76 * getContext()); // strictly for the wakelock hold 77 * // do the operation under test 78 * mAudioManager.setStreamVolume(STREAM_MUSIC, 79 * mAudioManager.getMinStreamVolume(STREAM_MUSIC), 0); 80 * // sleep and check until the volume has changed to what the test expects, 81 * // it will throw an Exception if that doesn't happen within 5s 82 * test.assertEqualsSleep( mAudioManager.getMinStreamVolume(STREAM_MUSIC), // expected value 83 * () -> mAudioManager.getStreamVolume(STREAM_MUSIC), 84 * "Observed volume not at min for MUSIC"); 85 * </pre> 86 */ 87 public static class SleepAssertIntEquals { 88 final long mMaxWaitMs; 89 final long mPeriodMs; 90 private PowerManager.WakeLock mWakeLock; 91 92 /** 93 * Constructor for the test utility 94 * @param maxWaitMs the maximum time this test will ever wait 95 * @param periodMs the period to sleep for in between test attempts, 96 * must be less than maxWaitMs 97 * @param context not retained, just for obtaining a partial wakelock from PowerManager 98 */ SleepAssertIntEquals(int maxWaitMs, int periodMs, Context context)99 SleepAssertIntEquals(int maxWaitMs, int periodMs, Context context) { 100 if (periodMs >= maxWaitMs) { 101 throw new IllegalArgumentException("Period must be lower than max wait time"); 102 } 103 mMaxWaitMs = maxWaitMs; 104 mPeriodMs = periodMs; 105 PowerManager pm = context.getSystemService(PowerManager.class); 106 mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "SleepAssertIntEquals"); 107 } 108 109 /** 110 * Compares the expected against the result of the lambda until they're equals, or unless 111 * the max wait time has elapsed, whichever happens first. On a timeout (int result wasn't 112 * as expected), the method asserts. 113 * @param expected the expected int value in the test 114 * @param result the function returning an int under test 115 * @param message the message to display when asserting 116 * @throws InterruptedException 117 */ assertEqualsSleep(int expected, IntSupplier result, String message)118 public void assertEqualsSleep(int expected, IntSupplier result, String message) 119 throws InterruptedException { 120 final long endMs = System.currentTimeMillis() + mMaxWaitMs; 121 try { 122 mWakeLock.acquire(); 123 int actual = Integer.MIN_VALUE; 124 while (System.currentTimeMillis() < endMs) { 125 actual = result.getAsInt(); 126 if (actual == expected) { 127 // test successful, stop 128 return; 129 } else { 130 // wait some more before expecting the test to be successful 131 Thread.sleep(mPeriodMs); 132 } 133 } 134 assertEquals(message, expected, actual); 135 } finally { 136 mWakeLock.release(); 137 } 138 } 139 } 140 141 /** 142 * A helper class to use when wanting to block in a test on audio recording starting/stopping 143 */ 144 static class AudioRecordingCallbackUtil extends AudioManager.AudioRecordingCallback { 145 boolean mCalled; 146 private final Object mConfigLock = new Object(); 147 List<AudioRecordingConfiguration> mConfigs; 148 private final int mTestSource; 149 private final int mTestSession; 150 private CountDownLatch mCountDownLatch; 151 reset()152 void reset() { 153 mCountDownLatch = new CountDownLatch(1); 154 mCalled = false; 155 synchronized (mConfigLock) { 156 mConfigs = new ArrayList<AudioRecordingConfiguration>(); 157 } 158 } 159 AudioRecordingCallbackUtil(int session, int source)160 AudioRecordingCallbackUtil(int session, int source) { 161 mTestSource = source; 162 mTestSession = session; 163 reset(); 164 } 165 166 @Override onRecordingConfigChanged(List<AudioRecordingConfiguration> configs)167 public void onRecordingConfigChanged(List<AudioRecordingConfiguration> configs) { 168 mCalled = true; 169 synchronized (mConfigLock) { 170 mConfigs = configs; 171 } 172 mCountDownLatch.countDown(); 173 } 174 await(long timeoutMs)175 void await(long timeoutMs) { 176 try { 177 mCountDownLatch.await(timeoutMs, TimeUnit.MILLISECONDS); 178 } catch (InterruptedException e) { 179 } 180 } 181 hasRecording(int session, int source)182 boolean hasRecording(int session, int source) { 183 synchronized (mConfigLock) { 184 for (AudioRecordingConfiguration config : mConfigs) { 185 if ((config.getClientAudioSessionId() == session) 186 && (config.getAudioSource() == source)) { 187 return true; 188 } 189 } 190 } 191 return false; 192 } 193 } 194 195 196 private static final List<Integer> MEDIA_DEVICE_TYPES = List.of( 197 AudioDeviceInfo.TYPE_BUILTIN_SPEAKER, 198 AudioDeviceInfo.TYPE_WIRED_HEADSET, 199 AudioDeviceInfo.TYPE_WIRED_HEADPHONES, 200 AudioDeviceInfo.TYPE_BLUETOOTH_A2DP, 201 AudioDeviceInfo.TYPE_USB_HEADSET, 202 AudioDeviceInfo.TYPE_BLE_HEADSET); 203 getMediaDevices()204 static List<AudioDeviceInfo> getMediaDevices() { 205 AudioManager am = InstrumentationRegistry.getInstrumentation() 206 .getContext().getSystemService(AudioManager.class); 207 208 List<AudioDeviceInfo> mediaDevices = new ArrayList(); 209 AudioDeviceInfo[] allDevices = am.getDevices(AudioManager.GET_DEVICES_OUTPUTS); 210 211 for (AudioDeviceInfo device : allDevices) { 212 if (MEDIA_DEVICE_TYPES.contains(device.getType())) { 213 mediaDevices.add(device); 214 } 215 } 216 return mediaDevices; 217 } 218 } 219