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