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