1 /*
2  * Copyright (C) 2019 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 android.media.AudioAttributes.ALLOW_CAPTURE_BY_ALL;
20 import static android.media.AudioAttributes.ALLOW_CAPTURE_BY_NONE;
21 import static android.media.AudioAttributes.ALLOW_CAPTURE_BY_SYSTEM;
22 
23 import static org.hamcrest.Matchers.greaterThan;
24 import static org.hamcrest.Matchers.lessThan;
25 import static org.junit.Assert.assertEquals;
26 import static org.junit.Assert.assertFalse;
27 import static org.junit.Assert.assertNotSame;
28 import static org.junit.Assert.assertThat;
29 import static org.junit.Assert.assertTrue;
30 import static org.junit.Assert.fail;
31 import static org.testng.Assert.assertThrows;
32 
33 import static java.util.stream.Collectors.toSet;
34 
35 import android.media.AudioAttributes;
36 import android.media.AudioAttributes.AttributeUsage;
37 import android.media.AudioAttributes.CapturePolicy;
38 import android.media.AudioDeviceInfo;
39 import android.media.AudioFormat;
40 import android.media.AudioManager;
41 import android.media.AudioPlaybackCaptureConfiguration;
42 import android.media.AudioRecord;
43 import android.media.MediaPlayer;
44 import android.media.cts.MediaProjectionActivity;
45 import android.media.projection.MediaProjection;
46 import android.os.Handler;
47 import android.os.Looper;
48 import android.platform.test.annotations.AppModeNonSdkSandbox;
49 import android.platform.test.annotations.Presubmit;
50 
51 import androidx.test.rule.ActivityTestRule;
52 
53 import com.android.compatibility.common.util.NonMainlineTest;
54 
55 import org.junit.Before;
56 import org.junit.Rule;
57 import org.junit.Test;
58 
59 import java.nio.ByteBuffer;
60 import java.nio.ShortBuffer;
61 import java.util.Arrays;
62 import java.util.Set;
63 import java.util.Stack;
64 import java.util.concurrent.CountDownLatch;
65 import java.util.concurrent.TimeUnit;
66 
67 /**
68  * Test audio playback capture through MediaProjection.
69  *
70  * The tests do the following:
71  *   - retrieve a MediaProjection through MediaProjectionActivity
72  *   - play some audio
73  *   - use that MediaProjection to record the audio playing
74  *   - check that some audio was recorded.
75  *
76  * Currently the test that some audio was recorded just check that at least one sample is non 0.
77  * A better check needs to be used, eg: compare the power spectrum.
78  */
79 @NonMainlineTest
80 @AppModeNonSdkSandbox(reason = "The sandbox cannot retrieve MediaProjection.")
81 public class AudioPlaybackCaptureTest {
82     private static final String TAG = "AudioPlaybackCaptureTest";
83     private static final int SAMPLE_RATE = 44100;
84     private static final int BUFFER_SIZE = SAMPLE_RATE * 2; // 1s at 44.1k/s 16bit mono
85 
86     private AudioManager mAudioManager;
87     private boolean mPlaybackBeforeCapture;
88     private int mUid; //< UID of this test
89     private MediaProjectionActivity mActivity;
90     private MediaProjection mMediaProjection;
91     @Rule
92     public ActivityTestRule<MediaProjectionActivity> mActivityRule =
93                 new ActivityTestRule<>(MediaProjectionActivity.class);
94 
95     private static class APCTestConfig {
96         public @AttributeUsage int[] matchingUsages;
97         public @AttributeUsage int[] excludeUsages;
98         public int[] matchingUids;
99         public int[] excludeUids;
build(MediaProjection projection)100         private AudioPlaybackCaptureConfiguration build(MediaProjection projection)
101                 throws Exception {
102             AudioPlaybackCaptureConfiguration.Builder apccBuilder =
103                     new AudioPlaybackCaptureConfiguration.Builder(projection);
104 
105             if (matchingUsages != null) {
106                 for (int usage : matchingUsages) {
107                     apccBuilder.addMatchingUsage(usage);
108                 }
109             }
110             if (excludeUsages != null) {
111                 for (int usage : excludeUsages) {
112                     apccBuilder.excludeUsage(usage);
113                 }
114             }
115             if (matchingUids != null) {
116                 for (int uid : matchingUids) {
117                     apccBuilder.addMatchingUid(uid);
118                 }
119             }
120             if (excludeUids != null) {
121                 for (int uid : excludeUids) {
122                     apccBuilder.excludeUid(uid);
123                 }
124             }
125             AudioPlaybackCaptureConfiguration config = apccBuilder.build();
126             assertCorreclyBuilt(config);
127             return config;
128         }
129 
assertCorreclyBuilt(AudioPlaybackCaptureConfiguration config)130         private void assertCorreclyBuilt(AudioPlaybackCaptureConfiguration config) {
131             assertEquals("matchingUsages",
132                     arraytoSet(matchingUsages), arraytoSet(config.getMatchingUsages()));
133             assertEquals("excludeUsages",
134                     arraytoSet(excludeUsages), arraytoSet(config.getExcludeUsages()));
135             assertEquals("matchingUids",
136                     arraytoSet(matchingUids), arraytoSet(config.getMatchingUids()));
137             assertEquals("excludeUids",
138                     arraytoSet(excludeUids), arraytoSet(config.getExcludeUids()));
139         }
140 
arraytoSet(int[] array)141         private static Set<Integer> arraytoSet(int[] array) {
142             return array == null ? Set.of() : Arrays.stream(array).boxed().collect(toSet());
143         }
144     };
145     private APCTestConfig mAPCTestConfig;
146 
147     @Before
setup()148     public void setup() throws Exception {
149         mPlaybackBeforeCapture = false;
150         mAPCTestConfig = new APCTestConfig();
151         mActivity = mActivityRule.getActivity();
152         mAudioManager = mActivity.getSystemService(AudioManager.class);
153         mUid = mActivity.getApplicationInfo().uid;
154         mMediaProjection = mActivity.waitForMediaProjection();
155     }
156 
createDefaultPlaybackCaptureRecord()157     private AudioRecord createDefaultPlaybackCaptureRecord() throws Exception {
158         return createPlaybackCaptureRecord(
159             new AudioFormat.Builder()
160                  .setEncoding(AudioFormat.ENCODING_PCM_16BIT)
161                  .setSampleRate(SAMPLE_RATE)
162                  .setChannelMask(AudioFormat.CHANNEL_IN_MONO)
163                  .build());
164     }
165 
createPlaybackCaptureRecord(AudioFormat audioFormat)166     private AudioRecord createPlaybackCaptureRecord(AudioFormat audioFormat) throws Exception {
167         AudioPlaybackCaptureConfiguration apcConfig = mAPCTestConfig.build(mMediaProjection);
168 
169         AudioRecord audioRecord = new AudioRecord.Builder()
170                 .setAudioPlaybackCaptureConfig(apcConfig)
171                 .setAudioFormat(audioFormat)
172                 .build();
173         assertEquals("AudioRecord failed to initialized", AudioRecord.STATE_INITIALIZED,
174                      audioRecord.getState());
175         return audioRecord;
176     }
177 
createMediaPlayer(@apturePolicy int capturePolicy, int resid, @AttributeUsage int usage)178     private MediaPlayer createMediaPlayer(@CapturePolicy int capturePolicy, int resid,
179                                           @AttributeUsage int usage) {
180         MediaPlayer mediaPlayer = MediaPlayer.create(
181                 mActivity,
182                 resid,
183                 new AudioAttributes.Builder()
184                     .setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
185                     .setUsage(usage)
186                     .setAllowedCapturePolicy(capturePolicy)
187                     .build(),
188                 mAudioManager.generateAudioSessionId());
189         mediaPlayer.setLooping(true);
190         return mediaPlayer;
191     }
192 
readToBuffer(AudioRecord audioRecord, int bufferSize)193     private static ByteBuffer readToBuffer(AudioRecord audioRecord, int bufferSize)
194             throws Exception {
195         assertEquals("AudioRecord is not recording", AudioRecord.RECORDSTATE_RECORDING,
196                      audioRecord.getRecordingState());
197         ByteBuffer buffer = ByteBuffer.allocateDirect(bufferSize);
198         int retry = 100;
199         boolean silence = true;
200         while (silence && buffer.hasRemaining()) {
201             assertNotSame(buffer.remaining() + "/" + bufferSize + "remaining", 0, retry--);
202             int written = audioRecord.read(buffer, buffer.remaining());
203             assertThat("audioRecord did not read frames", written, greaterThan(0));
204             for (int i = 0; i < written; i++) {
205                 silence &= buffer.get() == 0;
206             }
207         }
208         buffer.rewind();
209         return buffer;
210     }
211 
onlySilence(ShortBuffer buffer)212     private static boolean onlySilence(ShortBuffer buffer) {
213         while (buffer.hasRemaining()) {
214             if (buffer.get() != 0) {
215                 return false;
216             }
217         }
218         return true;
219     }
220 
testPlaybackCapture(@apturePolicy int capturePolicy, @AttributeUsage int playbackUsage, boolean dataPresent)221     public void testPlaybackCapture(@CapturePolicy int capturePolicy,
222                                     @AttributeUsage int playbackUsage,
223                                     boolean dataPresent) throws Exception {
224         MediaPlayer mediaPlayer = createMediaPlayer(capturePolicy, R.raw.testwav_16bit_44100hz,
225                                                     playbackUsage);
226         try {
227             if (mPlaybackBeforeCapture) {
228                 mediaPlayer.start();
229                 // Make sure the player is actually playing, thus forcing a rerouting
230                 Thread.sleep(100);
231             }
232 
233             AudioRecord audioRecord = createDefaultPlaybackCaptureRecord();
234 
235             try {
236                 audioRecord.startRecording();
237                 mediaPlayer.start();
238                 ByteBuffer rawBuffer = readToBuffer(audioRecord, BUFFER_SIZE);
239                 audioRecord.stop(); // Force an reroute
240                 mediaPlayer.stop();
241                 assertEquals(AudioRecord.RECORDSTATE_STOPPED, audioRecord.getRecordingState());
242                 if (dataPresent) {
243                     assertFalse("Expected data, but only silence was recorded",
244                                 onlySilence(rawBuffer.asShortBuffer()));
245                 } else {
246                     assertTrue("Expected silence, but some data was recorded",
247                                onlySilence(rawBuffer.asShortBuffer()));
248                 }
249             } finally {
250                 audioRecord.release();
251             }
252         } finally {
253             mediaPlayer.release();
254         }
255     }
256 
testPlaybackCapture(boolean allowCapture, @AttributeUsage int playbackUsage, boolean dataPresent)257     public void testPlaybackCapture(boolean allowCapture,
258                                     @AttributeUsage int playbackUsage,
259                                     boolean dataPresent) throws Exception {
260         if (allowCapture) {
261             testPlaybackCapture(ALLOW_CAPTURE_BY_ALL, playbackUsage, dataPresent);
262         } else {
263             testPlaybackCapture(ALLOW_CAPTURE_BY_SYSTEM, playbackUsage, dataPresent);
264             testPlaybackCapture(ALLOW_CAPTURE_BY_NONE, playbackUsage, dataPresent);
265 
266             try {
267                 mAudioManager.setAllowedCapturePolicy(ALLOW_CAPTURE_BY_SYSTEM);
268                 testPlaybackCapture(ALLOW_CAPTURE_BY_ALL, playbackUsage, dataPresent);
269                 mAudioManager.setAllowedCapturePolicy(ALLOW_CAPTURE_BY_NONE);
270                 testPlaybackCapture(ALLOW_CAPTURE_BY_ALL, playbackUsage, dataPresent);
271             } finally {
272                 // Do not impact followup test is case of failure
273                 mAudioManager.setAllowedCapturePolicy(ALLOW_CAPTURE_BY_ALL);
274             }
275         }
276     }
277 
278     private static final boolean OPT_IN = true;
279     private static final boolean OPT_OUT = false;
280 
281     private static final boolean EXPECT_DATA = true;
282     private static final boolean EXPECT_SILENCE = false;
283 
284     // We have explicit tests per usage testCaptureMatchingAllowedUsage*
285     private static final @AttributeUsage int[] ALLOWED_USAGES = new int[]{
286             AudioAttributes.USAGE_UNKNOWN,
287             AudioAttributes.USAGE_MEDIA,
288             AudioAttributes.USAGE_GAME
289     };
290     private static final @AttributeUsage int[] FORBIDEN_USAGES = new int[]{
291             AudioAttributes.USAGE_ALARM,
292             AudioAttributes.USAGE_ASSISTANCE_ACCESSIBILITY,
293             AudioAttributes.USAGE_ASSISTANCE_NAVIGATION_GUIDANCE,
294             AudioAttributes.USAGE_ASSISTANCE_SONIFICATION,
295             AudioAttributes.USAGE_ASSISTANT,
296             AudioAttributes.USAGE_NOTIFICATION,
297     };
298 
299     @Presubmit
300     @Test
testPlaybackCaptureFast()301     public void testPlaybackCaptureFast() throws Exception {
302         mAPCTestConfig.matchingUsages = new int[]{ AudioAttributes.USAGE_MEDIA };
303         testPlaybackCapture(OPT_IN, AudioAttributes.USAGE_MEDIA, EXPECT_DATA);
304         testPlaybackCapture(OPT_OUT, AudioAttributes.USAGE_MEDIA, EXPECT_SILENCE);
305     }
306 
307     @Test
testPlaybackCaptureRerouting()308     public void testPlaybackCaptureRerouting() throws Exception {
309         mPlaybackBeforeCapture = true;
310         mAPCTestConfig.matchingUsages = new int[]{ AudioAttributes.USAGE_MEDIA };
311         testPlaybackCapture(OPT_IN, AudioAttributes.USAGE_MEDIA, EXPECT_DATA);
312     }
313 
314     @Test(expected = IllegalArgumentException.class)
testMatchNothing()315     public void testMatchNothing() throws Exception {
316         testPlaybackCapture(OPT_IN, AudioAttributes.USAGE_UNKNOWN, EXPECT_SILENCE);
317     }
318 
319     @Test(expected = IllegalStateException.class)
testCombineUsages()320     public void testCombineUsages() throws Exception {
321         mAPCTestConfig.matchingUsages = new int[]{ AudioAttributes.USAGE_UNKNOWN };
322         mAPCTestConfig.excludeUsages = new int[]{ AudioAttributes.USAGE_MEDIA };
323         testPlaybackCapture(OPT_IN, AudioAttributes.USAGE_UNKNOWN, EXPECT_SILENCE);
324     }
325 
326     @Test(expected = IllegalStateException.class)
testCombineUid()327     public void testCombineUid() throws Exception {
328         mAPCTestConfig.matchingUids = new int[]{ mUid };
329         mAPCTestConfig.excludeUids = new int[]{ 0 };
330         testPlaybackCapture(OPT_IN, AudioAttributes.USAGE_UNKNOWN, EXPECT_SILENCE);
331     }
332 
333     // Allowed usage tests done individually to isolate failure and keep test duration < 30s.
334     @Test
testCaptureMatchingAllowedUsageUnknown()335     public void testCaptureMatchingAllowedUsageUnknown() throws Exception {
336         doTestCaptureMatchingAllowedUsage(AudioAttributes.USAGE_UNKNOWN);
337     }
338 
339     @Test
testCaptureMatchingAllowedUsageMedia()340     public void testCaptureMatchingAllowedUsageMedia() throws Exception {
341         doTestCaptureMatchingAllowedUsage(AudioAttributes.USAGE_MEDIA);
342     }
343 
344     @Test
testCaptureMatchingAllowedUsageGame()345     public void testCaptureMatchingAllowedUsageGame() throws Exception {
346         doTestCaptureMatchingAllowedUsage(AudioAttributes.USAGE_GAME);
347     }
348 
doTestCaptureMatchingAllowedUsage(int usage)349     private void doTestCaptureMatchingAllowedUsage(int usage) throws Exception {
350         mAPCTestConfig.matchingUsages = new int[]{ usage };
351         testPlaybackCapture(OPT_IN, usage, EXPECT_DATA);
352         testPlaybackCapture(OPT_OUT, usage, EXPECT_SILENCE);
353 
354         mAPCTestConfig.matchingUsages = ALLOWED_USAGES;
355         testPlaybackCapture(OPT_IN, usage, EXPECT_DATA);
356         testPlaybackCapture(OPT_OUT, usage, EXPECT_SILENCE);
357     }
358 
359     @Test
testCaptureMatchingForbidenUsage()360     public void testCaptureMatchingForbidenUsage() throws Exception {
361         for (int usage : FORBIDEN_USAGES) {
362             mAPCTestConfig.matchingUsages = new int[]{ usage };
363             testPlaybackCapture(OPT_IN, usage, EXPECT_SILENCE);
364 
365             mAPCTestConfig.matchingUsages = ALLOWED_USAGES;
366             testPlaybackCapture(OPT_IN, usage, EXPECT_SILENCE);
367         }
368     }
369 
370     @Test
testCaptureExcludeUsage()371     public void testCaptureExcludeUsage() throws Exception {
372         for (int usage : ALLOWED_USAGES) {
373             mAPCTestConfig.excludeUsages = new int[]{ usage };
374             testPlaybackCapture(OPT_IN, usage, EXPECT_SILENCE);
375 
376             mAPCTestConfig.excludeUsages = ALLOWED_USAGES;
377             testPlaybackCapture(OPT_IN, usage, EXPECT_SILENCE);
378 
379             mAPCTestConfig.excludeUsages = FORBIDEN_USAGES;
380             testPlaybackCapture(OPT_IN, usage, EXPECT_DATA);
381         }
382     }
383 
384     @Test
testCaptureMatchingUid()385     public void testCaptureMatchingUid() throws Exception {
386         mAPCTestConfig.matchingUids = new int[]{ mUid };
387         testPlaybackCapture(OPT_IN, AudioAttributes.USAGE_GAME, EXPECT_DATA);
388         testPlaybackCapture(OPT_OUT, AudioAttributes.USAGE_GAME, EXPECT_SILENCE);
389         testPlaybackCapture(OPT_IN, AudioAttributes.USAGE_VOICE_COMMUNICATION, EXPECT_SILENCE);
390 
391         mAPCTestConfig.matchingUids = new int[]{ 0 };
392         testPlaybackCapture(OPT_IN, AudioAttributes.USAGE_GAME, EXPECT_SILENCE);
393     }
394 
395     @Test
testCaptureExcludeUid()396     public void testCaptureExcludeUid() throws Exception {
397         mAPCTestConfig.excludeUids = new int[]{ 0 };
398         testPlaybackCapture(OPT_IN, AudioAttributes.USAGE_GAME, EXPECT_DATA);
399         testPlaybackCapture(OPT_OUT, AudioAttributes.USAGE_UNKNOWN, EXPECT_SILENCE);
400         testPlaybackCapture(OPT_IN, AudioAttributes.USAGE_VOICE_COMMUNICATION, EXPECT_SILENCE);
401 
402         mAPCTestConfig.excludeUids = new int[]{ mUid };
403         testPlaybackCapture(OPT_IN, AudioAttributes.USAGE_GAME, EXPECT_SILENCE);
404     }
405 
406     @Test(expected = UnsupportedOperationException.class)
testStoppedMediaProjection()407     public void testStoppedMediaProjection() throws Exception {
408         mMediaProjection.stop();
409         mAPCTestConfig.matchingUsages = new int[]{ AudioAttributes.USAGE_MEDIA };
410         testPlaybackCapture(OPT_IN, AudioAttributes.USAGE_MEDIA, EXPECT_DATA);
411     }
412 
413     @Test
testStopMediaProjectionDuringCapture()414     public void testStopMediaProjectionDuringCapture() throws Exception {
415         final int STOP_TIMEOUT_MS = 1000;
416 
417         mAPCTestConfig.matchingUsages = new int[]{ AudioAttributes.USAGE_MEDIA };
418 
419         MediaPlayer mediaPlayer = createMediaPlayer(ALLOW_CAPTURE_BY_ALL,
420                                                     R.raw.testwav_16bit_44100hz,
421                                                     AudioAttributes.USAGE_MEDIA);
422         mediaPlayer.start();
423 
424         AudioRecord audioRecord = createDefaultPlaybackCaptureRecord();
425         audioRecord.startRecording();
426         ByteBuffer rawBuffer = readToBuffer(audioRecord, BUFFER_SIZE);
427         assertFalse("Expected data, but only silence was recorded",
428                     onlySilence(rawBuffer.asShortBuffer()));
429 
430         final int nativeBufferSize = audioRecord.getBufferSizeInFrames()
431                                      * audioRecord.getChannelCount();
432 
433         // Stop the media projection
434         CountDownLatch stopCDL = new CountDownLatch(1);
435         mMediaProjection.registerCallback(new MediaProjection.Callback() {
436                 public void onStop() {
437                     stopCDL.countDown();
438                 }
439             }, new Handler(Looper.getMainLooper()));
440         mMediaProjection.stop();
441         assertTrue("Could not stop the MediaProjection in " + STOP_TIMEOUT_MS + "ms",
442                    stopCDL.await(STOP_TIMEOUT_MS, TimeUnit.MILLISECONDS));
443 
444         // With the remote submix disabled, no new samples should feed the track buffer.
445         // As a result, read() should fail after at most the total buffer size read.
446         // Even if the projection is stopped, the policy unregisteration is async,
447         // so double that to be on the conservative side.
448         final int MAX_READ_SIZE = 8 * nativeBufferSize;
449         int readSize = 0;
450         ByteBuffer buffer = ByteBuffer.allocateDirect(BUFFER_SIZE);
451         int status;
452         while ((status = audioRecord.read(buffer, BUFFER_SIZE)) > 0) {
453             readSize += status;
454             assertThat("audioRecord did not stop, current state is "
455                        + audioRecord.getRecordingState(), readSize, lessThan(MAX_READ_SIZE));
456         }
457         audioRecord.stop();
458         audioRecord.startRecording();
459 
460         // Check that the audioRecord can no longer receive audio
461         assertThat("Can still record after policy unregistration",
462                    audioRecord.read(buffer, BUFFER_SIZE), lessThan(0));
463 
464         audioRecord.release();
465         mediaPlayer.stop();
466         mediaPlayer.release();
467     }
468 
469 
470     @Test
testPlaybackCaptureDoS()471     public void testPlaybackCaptureDoS() throws Exception {
472         final int UPPER_BOUND_TO_CONCURENT_PLAYBACK_CAPTURE = 1000;
473         final int MIN_NB_OF_CONCURENT_PLAYBACK_CAPTURE = 5;
474 
475         mAPCTestConfig.matchingUsages = new int[]{ AudioAttributes.USAGE_MEDIA };
476 
477         Stack<AudioRecord> audioRecords = new Stack<>();
478         MediaPlayer mediaPlayer = createMediaPlayer(ALLOW_CAPTURE_BY_ALL,
479                                                     R.raw.testwav_16bit_44100hz,
480                                                     AudioAttributes.USAGE_MEDIA);
481         try {
482             mediaPlayer.start();
483 
484             // Lets create as many audio playback capture as we can
485             try {
486                 for (int i = 0; i < UPPER_BOUND_TO_CONCURENT_PLAYBACK_CAPTURE; i++) {
487                     audioRecords.push(createDefaultPlaybackCaptureRecord());
488                 }
489                 fail("Playback capture never failed even with " + audioRecords.size()
490                         + " concurrent ones. Are errors silently dropped ?");
491             } catch (Exception e) {
492                 assertThat("Number of supported concurrent playback capture", audioRecords.size(),
493                            greaterThan(MIN_NB_OF_CONCURENT_PLAYBACK_CAPTURE));
494             }
495 
496             // Should not be able to create a new audio playback capture record",
497             assertThrows(Exception.class, this::createDefaultPlaybackCaptureRecord);
498 
499             // Check that all record can all be started
500             for (AudioRecord audioRecord : audioRecords) {
501                 audioRecord.startRecording();
502                 assertEquals(AudioRecord.RECORDSTATE_RECORDING, audioRecord.getRecordingState());
503             }
504 
505             // Check that they all record audio. Since for a system under load it could
506             // take some time to establish the capture pipeline, allow for retries.
507             final int kDataAcquisitionAttempts = 3;
508             Stack<String> failedRecords = new Stack<>();
509             for (AudioRecord audioRecord : audioRecords) {
510                 boolean noData = true;
511                 for (int attempt = 0; attempt < kDataAcquisitionAttempts; attempt++) {
512                     ByteBuffer rawBuffer = readToBuffer(audioRecord, BUFFER_SIZE);
513                     if (!onlySilence(rawBuffer.asShortBuffer())) {
514                         noData = false;
515                         break;
516                     }
517                 }
518                 if (noData) {
519                     AudioDeviceInfo device = audioRecord.getRoutedDevice();
520                     if (device != null) {
521                         failedRecords.push(device.getAddress());
522                     } else {
523                         failedRecords.push("null device");
524                     }
525                 }
526             }
527             if (!failedRecords.empty()) {
528                 fail("Expected data, but only silence was recorded for " + failedRecords);
529             }
530 
531             // Stopping one AR must allow creating a new one
532             audioRecords.peek().stop();
533             audioRecords.pop().release();
534             final long SLEEP_AFTER_STOP_FOR_INACTIVITY_MS = 1000;
535             Thread.sleep(SLEEP_AFTER_STOP_FOR_INACTIVITY_MS);
536             audioRecords.push(createDefaultPlaybackCaptureRecord());
537 
538             // That new one must still be able to capture
539             audioRecords.peek().startRecording();
540             ByteBuffer rawBuffer = readToBuffer(audioRecords.peek(), BUFFER_SIZE);
541             assertFalse("Expected data, but only silence was recorded",
542                         onlySilence(rawBuffer.asShortBuffer()));
543 
544             // cleanup
545             mediaPlayer.stop();
546         } finally {
547             mediaPlayer.release();
548             try {
549                 for (AudioRecord audioRecord : audioRecords) {
550                     audioRecord.stop();
551                 }
552             } finally {
553                 for (AudioRecord audioRecord : audioRecords) {
554                     audioRecord.release();
555                 }
556             }
557         }
558     }
559 
560 }
561