1 /*
2  * Copyright 2017 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 com.google.common.truth.Truth.assertThat;
20 
21 import static org.junit.Assert.assertEquals;
22 import static org.junit.Assert.assertTrue;
23 import static org.junit.Assert.fail;
24 import static org.testng.Assert.assertThrows;
25 
26 import android.app.ActivityManager;
27 import android.content.Context;
28 import android.content.pm.PackageManager;
29 import android.media.AudioAttributes;
30 import android.media.AudioFormat;
31 import android.media.AudioManager;
32 import android.media.AudioTrack;
33 import android.media.MediaPlayer;
34 import android.media.VolumeShaper;
35 import android.media.audio.cts.R;
36 import android.media.cts.AudioHelper;
37 import android.os.Parcel;
38 import android.os.PowerManager;
39 import android.platform.test.annotations.AppModeFull;
40 import android.util.Log;
41 
42 import androidx.test.InstrumentationRegistry;
43 import androidx.test.filters.FlakyTest;
44 import androidx.test.filters.LargeTest;
45 import androidx.test.filters.SmallTest;
46 
47 import org.junit.Test;
48 import org.junit.runner.RunWith;
49 
50 import java.util.Arrays;
51 
52 import junitparams.JUnitParamsRunner;
53 import junitparams.Parameters;
54 
55 /**
56  * VolumeShaperTest is automated using VolumeShaper.getVolume() to verify that a ramp
57  * or a duck is at the expected volume level. Listening to some tests is also possible,
58  * as we logcat the expected volume change.
59  *
60  * To see the listening messages:
61  *
62  * adb logcat | grep VolumeShaperTest
63  */
64 @AppModeFull(reason = "TODO: evaluate and port to instant")
65 @RunWith(JUnitParamsRunner.class)
66 public class VolumeShaperTest {
67     private static final String TAG = "VolumeShaperTest";
68 
69     // ramp or duck time (duration) used in tests.
70     private static final long RAMP_TIME_MS = 3000;
71 
72     // volume tolerance for completion volume checks.
73     private static final float VOLUME_TOLERANCE = 0.0000001f;
74 
75     // volume difference permitted on replace() with join.
76     private static final float JOIN_VOLUME_TOLERANCE = 0.1f;
77 
78     // time to wait for player state change
79     private static final long WARMUP_TIME_MS = 300;
80 
81     private static final VolumeShaper.Configuration SILENCE =
82             new VolumeShaper.Configuration.Builder()
83                 .setInterpolatorType(VolumeShaper.Configuration.INTERPOLATOR_TYPE_LINEAR)
84                 .setCurve(new float[] { 0.f, 1.f } /* times */,
85                         new float[] { 0.f, 0.f } /* volumes */)
86                 .setDuration(RAMP_TIME_MS)
87                 .build();
88 
89     // Duck configurations go from 1.f down to 0.2f (not full ramp down).
90     private static final VolumeShaper.Configuration LINEAR_DUCK =
91             new VolumeShaper.Configuration.Builder()
92                 .setInterpolatorType(VolumeShaper.Configuration.INTERPOLATOR_TYPE_LINEAR)
93                 .setCurve(new float[] { 0.f, 1.f } /* times */,
94                         new float[] { 1.f, 0.2f } /* volumes */)
95                 .setDuration(RAMP_TIME_MS)
96                 .build();
97 
98     // Ramp configurations go from 0.f up to 1.f
99     private static final VolumeShaper.Configuration LINEAR_RAMP =
100             new VolumeShaper.Configuration.Builder(VolumeShaper.Configuration.LINEAR_RAMP)
101                 .setDuration(RAMP_TIME_MS)
102                 .build();
103 
104     private static final VolumeShaper.Configuration CUBIC_RAMP =
105             new VolumeShaper.Configuration.Builder(VolumeShaper.Configuration.CUBIC_RAMP)
106                 .setDuration(RAMP_TIME_MS)
107                 .build();
108 
109     private static final VolumeShaper.Configuration SINE_RAMP =
110             new VolumeShaper.Configuration.Builder(VolumeShaper.Configuration.SINE_RAMP)
111                 .setDuration(RAMP_TIME_MS)
112                 .build();
113 
114     private static final VolumeShaper.Configuration SCURVE_RAMP =
115             new VolumeShaper.Configuration.Builder(VolumeShaper.Configuration.SCURVE_RAMP)
116             .setDuration(RAMP_TIME_MS)
117             .build();
118 
119     // This linear ramp VolumeShaper runs on media time, not clock time.
120     // Note: for direct or offloaded audio, media time is not supported, so clock time is used.
121     private static final VolumeShaper.Configuration LINEAR_RAMP_MEDIA_TIME =
122             new VolumeShaper.Configuration.Builder(VolumeShaper.Configuration.LINEAR_RAMP)
123                     .setDuration(RAMP_TIME_MS)
124                     .setOptionFlags(0) // clears flags, operates on media time.
125                     .build();
126 
127     // internal use only
128     private static final VolumeShaper.Configuration LOG_RAMP =
129             new VolumeShaper.Configuration.Builder()
130                 .setInterpolatorType(VolumeShaper.Configuration.INTERPOLATOR_TYPE_LINEAR)
131                 .setOptionFlags(
132                         VolumeShaper.Configuration.OPTION_FLAG_VOLUME_IN_DBFS |
133                         VolumeShaper.Configuration.OPTION_FLAG_CLOCK_TIME)
134                 .setCurve(new float[] { 0.f, 1.f } /* times */,
135                         new float[] { -80.f, 0.f } /* volumes */)
136                 .setDuration(RAMP_TIME_MS)
137                 .build();
138 
139     // a step ramp is not continuous, so we have a different test for it.
140     private static final VolumeShaper.Configuration STEP_RAMP =
141             new VolumeShaper.Configuration.Builder()
142                 .setInterpolatorType(VolumeShaper.Configuration.INTERPOLATOR_TYPE_STEP)
143                 .setCurve(new float[] { 0.f, 1.f } /* times */,
144                         new float[] { 0.f, 1.f } /* volumes */)
145                 .setDuration(RAMP_TIME_MS)
146                 .build();
147 
148     private static final VolumeShaper.Configuration[] ALL_STANDARD_RAMPS = {
149         LINEAR_RAMP,
150         CUBIC_RAMP,
151         SINE_RAMP,
152         SCURVE_RAMP,
153     };
154 
155     private static final VolumeShaper.Configuration[] TEST_DUCKS = {
156         LINEAR_DUCK,
157     };
158 
159     // this ramp should result in non-monotonic behavior with a typical cubic spline.
160     private static final VolumeShaper.Configuration MONOTONIC_TEST =
161             new VolumeShaper.Configuration.Builder()
162                 .setInterpolatorType(VolumeShaper.Configuration.INTERPOLATOR_TYPE_CUBIC_MONOTONIC)
163                 .setCurve(new float[] { 0.f, 0.3f, 0.7f, 1.f } /* times */,
164                         new float[] { 0.f, 0.5f, 0.5f, 1.f } /* volumes */)
165                 .setDuration(RAMP_TIME_MS)
166                 .build();
167 
168     private static final VolumeShaper.Configuration MONOTONIC_TEST_FAIL =
169             new VolumeShaper.Configuration.Builder(MONOTONIC_TEST)
170                 .setInterpolatorType(VolumeShaper.Configuration.INTERPOLATOR_TYPE_CUBIC)
171                 .build();
172 
173     private static final VolumeShaper.Configuration[] MONOTONIC_RAMPS = {
174             MONOTONIC_TEST,
175             CUBIC_RAMP,
176             SCURVE_RAMP,
177             SINE_RAMP,
178     };
179 
180     private static final VolumeShaper.Operation[] ALL_STANDARD_OPERATIONS = {
181         VolumeShaper.Operation.PLAY,
182         VolumeShaper.Operation.REVERSE,
183     };
184 
hasAudioOutput()185     private boolean hasAudioOutput() {
186         return getContext().getPackageManager()
187             .hasSystemFeature(PackageManager.FEATURE_AUDIO_OUTPUT);
188     }
189 
isLowRamDevice()190     private boolean isLowRamDevice() {
191         return ((ActivityManager) getContext().getSystemService(Context.ACTIVITY_SERVICE))
192                 .isLowRamDevice();
193     }
194 
createSineAudioTrack(boolean allowOffload)195     private static AudioTrack createSineAudioTrack(boolean allowOffload) {
196         final int TEST_FORMAT = AudioFormat.ENCODING_PCM_FLOAT;
197         final int TEST_MODE = AudioTrack.MODE_STATIC;
198         final int TEST_SR = 48000;
199         final AudioFormat format = new AudioFormat.Builder()
200                 .setChannelMask(AudioFormat.CHANNEL_OUT_MONO)
201                 .setEncoding(TEST_FORMAT)
202                 .setSampleRate(TEST_SR)
203                 .build();
204 
205         final int frameCount = AudioHelper.frameCountFromMsec(100 /*ms*/, format);
206         final int frameSize = AudioHelper.frameSizeFromFormat(format);
207         final int usage = allowOffload
208                 ? AudioAttributes.USAGE_MEDIA : AudioAttributes.USAGE_ALARM;
209 
210         final AudioTrack audioTrack = new AudioTrack.Builder()
211             .setAudioAttributes(new AudioAttributes.Builder()
212                     .setUsage(usage)
213                     .build())
214             .setAudioFormat(format)
215             .setBufferSizeInBytes(frameCount * frameSize)
216             .setTransferMode(TEST_MODE)
217             .build();
218         // create float array and write it
219         final int sampleCount = frameCount * format.getChannelCount();
220         final float[] vaf = AudioHelper.createSoundDataInFloatArray(
221                 sampleCount, TEST_SR,
222                 600 * format.getChannelCount() /* frequency */, 0 /* sweep */);
223         assertEquals(vaf.length, audioTrack.write(vaf, 0 /* offsetInFloats */, vaf.length,
224                 AudioTrack.WRITE_NON_BLOCKING));
225         audioTrack.setLoopPoints(0, frameCount, -1 /* loopCount */);
226         return audioTrack;
227     }
228 
createMediaPlayer(boolean offloaded)229     private MediaPlayer createMediaPlayer(boolean offloaded) {
230         // MP3 resource should be greater than 1m to introduce offloading
231         final int RESOURCE_ID = R.raw.test1m1s;
232 
233         final MediaPlayer mediaPlayer = MediaPlayer.create(getContext(),
234                 RESOURCE_ID,
235                 new AudioAttributes.Builder()
236                     .setUsage(offloaded ?
237                             AudioAttributes.USAGE_MEDIA  // offload allowed
238                             : AudioAttributes.USAGE_NOTIFICATION) // offload not allowed
239                     .build(),
240                 new AudioManager(getContext()).generateAudioSessionId());
241         mediaPlayer.setWakeMode(getContext(), PowerManager.PARTIAL_WAKE_LOCK);
242         mediaPlayer.setLooping(true);
243         return mediaPlayer;
244     }
245 
checkEqual(String testName, VolumeShaper.Configuration expected, VolumeShaper.Configuration actual)246     private static void checkEqual(String testName,
247             VolumeShaper.Configuration expected, VolumeShaper.Configuration actual) {
248         assertEquals(testName + " configuration should be equal",
249                 expected, actual);
250         assertEquals(testName + " configuration.hashCode() should be equal",
251                 expected.hashCode(), actual.hashCode());
252         assertEquals(testName + " configuration.toString() should be equal",
253                 expected.toString(), actual.toString());
254     }
255 
checkNotEqual(String testName, VolumeShaper.Configuration notEqual, VolumeShaper.Configuration actual)256     private static void checkNotEqual(String testName,
257             VolumeShaper.Configuration notEqual, VolumeShaper.Configuration actual) {
258         assertTrue(testName + " configuration should not be equal",
259                 !actual.equals(notEqual));
260         assertTrue(testName + " configuration.hashCode() should not be equal",
261                 actual.hashCode() != notEqual.hashCode());
262         assertTrue(testName + " configuration.toString() should not be equal",
263                 !actual.toString().equals(notEqual.toString()));
264     }
265 
266     // generic player class to simplify testing
267     private interface Player extends AutoCloseable {
start()268         void start();
pause()269         void pause();
flush()270         void flush();
stop()271         void stop();
close()272         @Override void close();
createVolumeShaper(VolumeShaper.Configuration configuration)273         VolumeShaper createVolumeShaper(VolumeShaper.Configuration configuration);
name()274         String name();
275     }
276 
277     private static class AudioTrackPlayer implements Player {
AudioTrackPlayer(boolean allowOffload)278         AudioTrackPlayer(boolean allowOffload) {
279             mTrack = createSineAudioTrack(allowOffload);
280             mName = new String("AudioTrack");
281         }
282 
start()283         @Override public void start() {
284             mTrack.play();
285         }
286 
pause()287         @Override public void pause() {
288             mTrack.pause();
289         }
290 
flush()291         @Override public void flush() {
292             mTrack.flush();
293         }
294 
stop()295         @Override public void stop() {
296             mTrack.stop();
297         }
298 
close()299         @Override public void close() {
300             mTrack.release();
301         }
302 
303         @Override
createVolumeShaper(VolumeShaper.Configuration configuration)304         public VolumeShaper createVolumeShaper(VolumeShaper.Configuration configuration) {
305             return mTrack.createVolumeShaper(configuration);
306         }
307 
name()308         @Override public String name() {
309             return mName;
310         }
311 
312         private final AudioTrack mTrack;
313         private final String mName;
314     }
315 
316     // State management for MediaPlayer
317     private enum State {
318         STOPPED,
319         PAUSED,
320         PLAYING,
321         CLOSED,
322     }
323 
324     private class MediaPlayerPlayer implements Player {
MediaPlayerPlayer(boolean offloaded)325         public MediaPlayerPlayer(boolean offloaded) {
326             mPlayer = createMediaPlayer(offloaded);
327             mName = new String("MediaPlayer" + (offloaded ? "Offloaded" : "NonOffloaded"));
328             mState = State.STOPPED;
329         }
330 
start()331         @Override public void start() {
332             mPlayer.start();
333             mState = State.PLAYING;
334         }
335 
pause()336         @Override public void pause() {
337             mPlayer.pause();
338             mState = State.PAUSED;
339         }
340 
flush()341         @Override public void flush() {
342             if (mState == State.PAUSED) {
343                 // On MediaPlayer, seek can be called while playing, too.
344                 mPlayer.seekTo(0 /* msec */, MediaPlayer.SEEK_PREVIOUS_SYNC);
345             }
346         }
347 
stop()348         @Override public void stop() {
349             mPlayer.stop();
350             mState = State.STOPPED;
351         }
352 
close()353         @Override public void close() {
354             mPlayer.release();
355             mState = State.CLOSED;
356         }
357 
358         @Override
createVolumeShaper(VolumeShaper.Configuration configuration)359         public VolumeShaper createVolumeShaper(VolumeShaper.Configuration configuration) {
360             return mPlayer.createVolumeShaper(configuration);
361         }
362 
name()363         @Override public String name() {
364             return mName;
365         }
366 
367         private final MediaPlayer mPlayer;
368         private final String mName;
369         private State mState;
370     }
371 
372     private static final int PLAYER_TYPES = 3;
373     private static final int PLAYER_TYPE_AUDIO_TRACK = 0;
374     private static final int PLAYER_TYPE_MEDIA_PLAYER_NON_OFFLOADED = 1;
375     private static final int PLAYER_TYPE_MEDIA_PLAYER_OFFLOADED = 2;
376     private static final int PLAYER_TYPE_AUDIO_TRACK_NON_OFFLOADED = 3;
377 
createPlayer(int type)378     private Player createPlayer(int type) {
379         switch (type) {
380             case PLAYER_TYPE_AUDIO_TRACK:
381                 return new AudioTrackPlayer(true /* allowOffload */);
382             case PLAYER_TYPE_AUDIO_TRACK_NON_OFFLOADED:
383                 return new AudioTrackPlayer(false /* allowOffload */);
384             case PLAYER_TYPE_MEDIA_PLAYER_NON_OFFLOADED:
385                 return new MediaPlayerPlayer(false /* offloaded */);
386             case PLAYER_TYPE_MEDIA_PLAYER_OFFLOADED:
387                 return new MediaPlayerPlayer(true /* offloaded */);
388             default:
389                 return null;
390         }
391     }
392 
testBuildRamp(int points)393     private static void testBuildRamp(int points) {
394         float[] ramp = new float[points];
395         final float fscale = 1.f / (points - 1);
396         for (int i = 0; i < points; ++i) {
397             ramp[i] = i * fscale;
398         }
399         ramp[points - 1] = 1.f;
400         // does it build?
401         final VolumeShaper.Configuration config = new VolumeShaper.Configuration.Builder()
402                 .setCurve(ramp, ramp)
403                 .build();
404     }
405 
406     @SmallTest
407     @Test
testVolumeShaperConfigurationBuilder()408     public void testVolumeShaperConfigurationBuilder() throws Exception {
409         final String TEST_NAME = "testVolumeShaperConfigurationBuilder";
410 
411         // Verify that IllegalStateExceptions are properly triggered
412         // for methods with no arguments.
413 
414         Log.d(TAG, TEST_NAME + " configuration builder should throw ISE if no curve specified");
415         assertThrows(IllegalStateException.class,
416                 new VolumeShaper.Configuration.Builder()
417                     ::build);
418 
419         assertThrows(IllegalStateException.class,
420                 new VolumeShaper.Configuration.Builder()
421                     ::invertVolumes);
422 
423         assertThrows(IllegalStateException.class,
424                 new VolumeShaper.Configuration.Builder()
425                     ::reflectTimes);
426 
427         Log.d(TAG, TEST_NAME + " configuration builder should IAE on invalid curve");
428         // Verify IllegalArgumentExceptions are properly triggered
429         // for methods with arguments.
430         final float[] ohOne = { 0.f, 1.f };
431         final float[][] invalidCurves = {
432                 { -1.f, 1.f },
433                 { 0.5f },
434                 { 0.f, 2.f },
435         };
436         for (float[] invalidCurve : invalidCurves) {
437             assertThrows(IllegalArgumentException.class,
438                     () -> {
439                         new VolumeShaper.Configuration.Builder()
440                             .setCurve(invalidCurve, ohOne)
441                             .build(); });
442 
443             assertThrows(IllegalArgumentException.class,
444                     () -> {
445                         new VolumeShaper.Configuration.Builder()
446                             .setCurve(ohOne, invalidCurve)
447                             .build(); });
448         }
449 
450         Log.d(TAG, TEST_NAME + " configuration builder should throw IAE on invalid duration");
451         assertThrows(IllegalArgumentException.class,
452                 () -> {
453                     new VolumeShaper.Configuration.Builder()
454                         .setCurve(ohOne, ohOne)
455                         .setDuration(-1)
456                         .build(); });
457 
458         Log.d(TAG, TEST_NAME + " configuration builder should throw IAE on invalid interpolator");
459         assertThrows(IllegalArgumentException.class,
460                 () -> {
461                     new VolumeShaper.Configuration.Builder()
462                         .setCurve(ohOne, ohOne)
463                         .setInterpolatorType(-1)
464                         .build(); });
465 
466         // Verify defaults.
467         // Use the Builder with setCurve(ohOne, ohOne).
468         final VolumeShaper.Configuration config =
469                 new VolumeShaper.Configuration.Builder().setCurve(ohOne, ohOne).build();
470         assertEquals(TEST_NAME + " default interpolation should be cubic",
471                 VolumeShaper.Configuration.INTERPOLATOR_TYPE_CUBIC, config.getInterpolatorType());
472         assertEquals(TEST_NAME + " default duration should be 1000 ms",
473                 1000, config.getDuration());
474         assertTrue(TEST_NAME + " times should be { 0.f, 1.f }",
475                 Arrays.equals(ohOne, config.getTimes()));
476         assertTrue(TEST_NAME + " volumes should be { 0.f, 1.f }",
477                 Arrays.equals(ohOne, config.getVolumes()));
478 
479         // Due to precision problems, we cannot have ramps that do not have
480         // perfect binary representation for equality comparison.
481         // (For example, 0.1 is a repeating mantissa in binary,
482         //  but 0.25, 0.5 can be expressed with few mantissa bits).
483         final float[] binaryCurve1 = { 0.f, 0.25f, 0.5f, 0.625f,  1.f };
484         final float[] binaryCurve2 = { 0.f, 0.125f, 0.375f, 0.75f, 1.f };
485         final VolumeShaper.Configuration[] BINARY_RAMPS = {
486             LINEAR_RAMP,
487             CUBIC_RAMP,
488             new VolumeShaper.Configuration.Builder()
489                     .setCurve(binaryCurve1, binaryCurve2)
490                     .build(),
491         };
492 
493         // Verify volume inversion and time reflection work as expected
494         // with ramps (which start at { 0.f, 0.f } and end at { 1.f, 1.f }).
495         for (VolumeShaper.Configuration testRamp : BINARY_RAMPS) {
496             VolumeShaper.Configuration ramp;
497             ramp = new VolumeShaper.Configuration.Builder(testRamp).build();
498             checkEqual(TEST_NAME, testRamp, ramp);
499 
500             ramp = new VolumeShaper.Configuration.Builder(testRamp)
501                     .setDuration(10)
502                     .build();
503             checkNotEqual(TEST_NAME, testRamp, ramp);
504 
505             ramp = new VolumeShaper.Configuration.Builder(testRamp).build();
506             checkEqual(TEST_NAME, testRamp, ramp);
507 
508             ramp = new VolumeShaper.Configuration.Builder(testRamp)
509                     .invertVolumes()
510                     .build();
511             checkNotEqual(TEST_NAME, testRamp, ramp);
512 
513             ramp = new VolumeShaper.Configuration.Builder(testRamp)
514                     .invertVolumes()
515                     .invertVolumes()
516                     .build();
517             checkEqual(TEST_NAME, testRamp, ramp);
518 
519             ramp = new VolumeShaper.Configuration.Builder(testRamp)
520                     .reflectTimes()
521                     .build();
522             checkNotEqual(TEST_NAME, testRamp, ramp);
523 
524             ramp = new VolumeShaper.Configuration.Builder(testRamp)
525                     .reflectTimes()
526                     .reflectTimes()
527                     .build();
528             checkEqual(TEST_NAME, testRamp, ramp);
529 
530             // check scaling start and end volumes
531             ramp = new VolumeShaper.Configuration.Builder(testRamp)
532                     .scaleToStartVolume(0.5f)
533                     .build();
534             checkNotEqual(TEST_NAME, testRamp, ramp);
535 
536             ramp = new VolumeShaper.Configuration.Builder(testRamp)
537                     .scaleToStartVolume(0.5f)
538                     .scaleToStartVolume(0.f)
539                     .build();
540             checkEqual(TEST_NAME, testRamp, ramp);
541 
542             ramp = new VolumeShaper.Configuration.Builder(testRamp)
543                     .scaleToStartVolume(0.5f)
544                     .scaleToEndVolume(0.f)
545                     .scaleToStartVolume(1.f)
546                     .invertVolumes()
547                     .build();
548             checkEqual(TEST_NAME, testRamp, ramp);
549         }
550 
551         // check that getMaximumCurvePoints() returns the correct value
552         final int maxPoints = VolumeShaper.Configuration.getMaximumCurvePoints();
553 
554         testBuildRamp(maxPoints); // no exceptions here.
555 
556         if (maxPoints < Integer.MAX_VALUE) {
557             Log.d(TAG, TEST_NAME + " configuration builder "
558                     + "should throw IAE if getMaximumCurvePoints() exceeded");
559             assertThrows(IllegalArgumentException.class,
560                     () -> { testBuildRamp(maxPoints + 1); });
561         }
562     } // testVolumeShaperConfigurationBuilder
563 
564     @SmallTest
565     @Test
testVolumeShaperConfigurationParcelable()566     public void testVolumeShaperConfigurationParcelable() throws Exception {
567         final String TEST_NAME = "testVolumeShaperConfigurationParcelable";
568 
569         for (VolumeShaper.Configuration config : ALL_STANDARD_RAMPS) {
570             assertEquals(TEST_NAME + " no parceled file descriptors",
571                     0 /* expected */, config.describeContents());
572 
573             final Parcel srcParcel = Parcel.obtain();
574             config.writeToParcel(srcParcel, 0 /* flags */);
575 
576             final byte[] marshallBuffer = srcParcel.marshall();
577 
578             final Parcel dstParcel = Parcel.obtain();
579             dstParcel.unmarshall(marshallBuffer, 0 /* offset */, marshallBuffer.length);
580             dstParcel.setDataPosition(0);
581 
582             final VolumeShaper.Configuration restoredConfig =
583                     VolumeShaper.Configuration.CREATOR.createFromParcel(dstParcel);
584             assertEquals(TEST_NAME +
585                     " marshalled/restored VolumeShaper.Configuration should match",
586                     config, restoredConfig);
587         }
588     } // testVolumeShaperConfigurationParcelable
589 
590     @SmallTest
591     @Test
testVolumeShaperOperationParcelable()592     public void testVolumeShaperOperationParcelable() throws Exception {
593         final String TEST_NAME = "testVolumeShaperOperationParcelable";
594 
595         for (VolumeShaper.Operation operation : ALL_STANDARD_OPERATIONS) {
596             assertEquals(TEST_NAME + " no parceled file descriptors",
597                     0 /* expected */, operation.describeContents());
598 
599             final Parcel srcParcel = Parcel.obtain();
600             operation.writeToParcel(srcParcel, 0 /* flags */);
601 
602             final byte[] marshallBuffer = srcParcel.marshall();
603 
604             final Parcel dstParcel = Parcel.obtain();
605             dstParcel.unmarshall(marshallBuffer, 0 /* offset */, marshallBuffer.length);
606             dstParcel.setDataPosition(0);
607 
608             final VolumeShaper.Operation restoredOperation =
609                     VolumeShaper.Operation.CREATOR.createFromParcel(dstParcel);
610             assertEquals(TEST_NAME +
611                     " marshalled/restored VolumeShaper.Operation should match",
612                     operation, restoredOperation);
613         }
614     } // testVolumeShaperOperationParcelable
615 
616     // This tests that we can't create infinite shapers and cause audioserver
617     // to crash due to memory or performance issues.  Typically around 16 app based
618     // shapers are allowed by the audio server.
619     @SmallTest
620     @Test
testMaximumShapers()621     public void testMaximumShapers() {
622         final String TEST_NAME = "testMaximumShapers";
623         if (!hasAudioOutput()) {
624             Log.w(TAG, "AUDIO_OUTPUT feature not found. This system might not have a valid "
625                     + "audio output HAL");
626             return;
627         }
628 
629         final int WAY_TOO_MANY_SHAPERS = 1000;
630 
631         for (int p = 0; p < PLAYER_TYPES; ++p) {
632             try (Player player = createPlayer(p)) {
633                 final String testName = TEST_NAME + " " + player.name();
634                 final VolumeShaper[] shapers = new VolumeShaper[WAY_TOO_MANY_SHAPERS];
635                 int i = 0;
636                 try {
637                     for (; i < shapers.length; ++i) {
638                         shapers[i] = player.createVolumeShaper(SILENCE);
639                     }
640                     fail(testName + " should not be able to create "
641                             + shapers.length + " shapers");
642                 } catch (IllegalStateException ise) {
643                     Log.d(TAG, testName + " " + i + " shapers created before failure (OK)");
644                 }
645             }
646             // volume shapers close when player closes.
647         }
648     } // testMaximumShapers
649 
650     @Test
testDuckAudioTrack()651     public void testDuckAudioTrack() throws Exception {
652         try (Player player = createPlayer(PLAYER_TYPE_AUDIO_TRACK)) {
653             runTestDuckPlayer("testDuckAudioTrack", player);
654         }
655     }
656 
657     @Test
testDuckMediaPlayerNonOffloaded()658     public void testDuckMediaPlayerNonOffloaded() throws Exception {
659         try (Player player = createPlayer(PLAYER_TYPE_MEDIA_PLAYER_NON_OFFLOADED)) {
660             runTestDuckPlayer("testDuckMediaPlayerNonOffloaded", player);
661         }
662     }
663 
664     @Test
testDuckMediaPlayerOffloaded()665     public void testDuckMediaPlayerOffloaded() throws Exception {
666         try (Player player = createPlayer(PLAYER_TYPE_MEDIA_PLAYER_OFFLOADED)) {
667             runTestDuckPlayer("testDuckMediaPlayerOffloaded", player);
668         }
669     }
670 
runTestDuckPlayer(String testName, Player player)671     private void runTestDuckPlayer(String testName, Player player) throws Exception {
672         if (!hasAudioOutput()) {
673             Log.w(TAG, "AUDIO_OUTPUT feature not found. This system might not have a valid "
674                     + "audio output HAL");
675             return;
676         }
677 
678         try (VolumeShaper volumeShaper = player.createVolumeShaper(SILENCE)) {
679             Log.d(TAG, testName + " starting");
680             player.start();
681             Thread.sleep(WARMUP_TIME_MS);
682 
683             runDuckTest(testName, volumeShaper);
684             runCloseTest(testName, volumeShaper);
685         }
686     }
687 
getAllStandardRamps()688     private VolumeShaper.Configuration[] getAllStandardRamps() {
689         return ALL_STANDARD_RAMPS;
690     }
691 
692     // Parameters are indices to ALL_STANDARD_RAMPS configuration array.
693     @Test
694     @Parameters(method = "getAllStandardRamps")
testRampAudioTrack( VolumeShaper.Configuration configuration)695     public void testRampAudioTrack(
696             VolumeShaper.Configuration configuration) throws Exception {
697         try (Player player = createPlayer(PLAYER_TYPE_AUDIO_TRACK)) {
698             runTestRampPlayer("testRampAudioTrack", player, configuration);
699         }
700     }
701 
702     // Parameters are indices to ALL_STANDARD_RAMPS configuration array.
703     @Test
704     @Parameters(method = "getAllStandardRamps")
testRampMediaPlayerNonOffloaded( VolumeShaper.Configuration configuration)705     public void testRampMediaPlayerNonOffloaded(
706             VolumeShaper.Configuration configuration) throws Exception {
707         try (Player player = createPlayer(PLAYER_TYPE_MEDIA_PLAYER_NON_OFFLOADED)) {
708             runTestRampPlayer("testRampMediaPlayerNonOffloaded", player, configuration);
709         }
710     }
711 
712     // Parameters are indices to ALL_STANDARD_RAMPS configuration array.
713     @Test
714     @Parameters(method = "getAllStandardRamps")
testRampMediaPlayerOffloaded( VolumeShaper.Configuration configuration)715     public void testRampMediaPlayerOffloaded(
716             VolumeShaper.Configuration configuration) throws Exception {
717         try (Player player = createPlayer(PLAYER_TYPE_MEDIA_PLAYER_OFFLOADED)) {
718             runTestRampPlayer("testRampMediaPlayerOffloaded", player, configuration);
719         }
720     }
721 
runTestRampPlayer( String testName, Player player, VolumeShaper.Configuration configuration)722     private void runTestRampPlayer(
723             String testName, Player player, VolumeShaper.Configuration configuration)
724             throws Exception {
725         if (!hasAudioOutput()) {
726             Log.w(TAG, "AUDIO_OUTPUT feature not found. This system might not have a valid "
727                     + "audio output HAL");
728             return;
729         }
730 
731         try (VolumeShaper volumeShaper = player.createVolumeShaper(SILENCE)) {
732             Log.d(TAG, testName + " starting");
733             player.start();
734             Thread.sleep(WARMUP_TIME_MS);
735 
736             runRampTest(testName, volumeShaper, configuration);
737             runCloseTest(testName, volumeShaper);
738         }
739     }
740 
741     @FlakyTest
742     @Test
testCornerCaseAudioTrack()743     public void testCornerCaseAudioTrack() throws Exception {
744         try (Player player = createPlayer(PLAYER_TYPE_AUDIO_TRACK)) {
745             runTestCornerCasePlayer("testCornerCaseAudioTrack", player);
746         }
747     }
748 
749     @FlakyTest
750     @Test
testCornerCaseMediaPlayerNonOffloaded()751     public void testCornerCaseMediaPlayerNonOffloaded() throws Exception {
752         try (Player player = createPlayer(PLAYER_TYPE_MEDIA_PLAYER_NON_OFFLOADED)) {
753             runTestCornerCasePlayer("testCornerCaseMediaPlayerNonOffloaded", player);
754         }
755     }
756 
757     @FlakyTest
758     @Test
testCornerCaseMediaPlayerOffloaded()759     public void testCornerCaseMediaPlayerOffloaded() throws Exception {
760         try (Player player = createPlayer(PLAYER_TYPE_MEDIA_PLAYER_OFFLOADED)) {
761             runTestCornerCasePlayer("testCornerCaseMediaPlayerOffloaded", player);
762         }
763     }
764 
runTestCornerCasePlayer(String testName, Player player)765     private void runTestCornerCasePlayer(String testName, Player player) throws Exception {
766         if (!hasAudioOutput()) {
767             Log.w(TAG, "AUDIO_OUTPUT feature not found. This system might not have a valid "
768                     + "audio output HAL");
769             return;
770         }
771 
772         final VolumeShaper.Configuration config = LINEAR_RAMP;
773         VolumeShaper volumeShaper = null;
774         try {
775             volumeShaper = player.createVolumeShaper(config);
776 
777             runStartIdleTest(testName, volumeShaper, player);
778             runRampCornerCaseTest(testName, volumeShaper, config);
779             runCloseTest(testName, volumeShaper);
780 
781             Log.d(TAG, testName + " recreating VolumeShaper and repeating with pause");
782             volumeShaper = player.createVolumeShaper(config);
783             player.pause();
784             Thread.sleep(100 /* millis */);
785             runStartIdleTest(testName, volumeShaper, player);
786 
787             // volumeShaper not explicitly closed, will close upon finalize or player close.
788             Log.d(TAG, testName + " recreating VolumeShaper and repeating with stop");
789             volumeShaper = player.createVolumeShaper(config);
790             player.stop();
791             Thread.sleep(100 /* millis */);
792             runStartIdleTest(testName, volumeShaper, player);
793 
794             Log.d(TAG, testName + " closing Player before VolumeShaper");
795             player.close();
796             runCloseTest2(testName, volumeShaper);
797         } finally {
798             if (volumeShaper != null) {
799                 volumeShaper.close();
800             }
801         }
802     } // runTestCornerCasePlayer
803 
804     @FlakyTest
805     @LargeTest
806     @Test
testPlayerCornerCase2()807     public void testPlayerCornerCase2() throws Exception {
808         final String TEST_NAME = "testPlayerCornerCase2";
809         if (!hasAudioOutput()) {
810             Log.w(TAG, "AUDIO_OUTPUT feature not found. This system might not have a valid "
811                     + "audio output HAL");
812             return;
813         }
814 
815         final VolumeShaper.Configuration config = LINEAR_RAMP;
816 
817         for (int p = 0; p < PLAYER_TYPES; ++p) {
818             Player player = null;
819             VolumeShaper volumeShaper = null;
820             try {
821                 player = createPlayer(p);
822                 volumeShaper = player.createVolumeShaper(config);
823                 final String testName = TEST_NAME + " " + player.name();
824 
825                 runStartSyncTest(testName, volumeShaper, player);
826                 runCloseTest(testName, volumeShaper);
827 
828                 Log.d(TAG, testName + " recreating VolumeShaper and repeating with pause");
829                 volumeShaper = player.createVolumeShaper(config);
830                 player.pause();
831                 Thread.sleep(100 /* millis */);
832                 runStartSyncTest(testName, volumeShaper, player);
833 
834                 Log.d(TAG, testName + " closing Player before VolumeShaper");
835                 player.close();
836                 runCloseTest2(testName, volumeShaper);
837             } finally {
838                 if (volumeShaper != null) {
839                     volumeShaper.close();
840                 }
841                 if (player != null) {
842                     player.close();
843                 }
844             }
845         }
846     } // testPlayerCornerCase2
847 
848     @FlakyTest
849     @LargeTest
850     @Test
testPlayerJoin()851     public void testPlayerJoin() throws Exception {
852         final String TEST_NAME = "testPlayerJoin";
853         if (!hasAudioOutput()) {
854             Log.w(TAG, "AUDIO_OUTPUT feature not found. This system might not have a valid "
855                     + "audio output HAL");
856             return;
857         }
858 
859         for (int p = 0; p < PLAYER_TYPES; ++p) {
860             try (   Player player = createPlayer(p);
861                     VolumeShaper volumeShaper = player.createVolumeShaper(SILENCE);
862                     ) {
863                 final String testName = TEST_NAME + " " + player.name();
864                 volumeShaper.apply(VolumeShaper.Operation.PLAY);
865                 player.start();
866                 Thread.sleep(WARMUP_TIME_MS);
867 
868                 Log.d(TAG, " we join several LINEAR_RAMPS together "
869                         + " this effectively is one LINEAR_RAMP (volume increasing).");
870                 final long durationMs = 5000;
871                 final long incrementMs = 1000;
872                 for (long i = 0; i < durationMs; i += incrementMs) {
873                     Log.d(TAG, testName + " Play - join " + i);
874                     volumeShaper.replace(new VolumeShaper.Configuration.Builder(LINEAR_RAMP)
875                                     .setDuration(durationMs - i)
876                                     .build(),
877                             VolumeShaper.Operation.PLAY, true /* join */);
878                     assertEquals(testName + " linear ramp should continue on join",
879                             (float)i / durationMs, volumeShaper.getVolume(), 0.05 /* epsilon */);
880                     Thread.sleep(incrementMs);
881                 }
882                 Log.d(TAG, testName + "volume at max level now (closing player)");
883             }
884         }
885     } // testPlayerJoin
886 
getMonotonicRamps()887     private VolumeShaper.Configuration[] getMonotonicRamps() {
888         return MONOTONIC_RAMPS;
889     }
890 
891     // Parameters are indices to MONOTONIC_RAMPS configuration array.
892     @Test
893     @Parameters(method = "getMonotonicRamps")
testCubicMonotonicAudioTrack( VolumeShaper.Configuration configuration)894     public void testCubicMonotonicAudioTrack(
895             VolumeShaper.Configuration configuration) throws Exception {
896         try (Player player = createPlayer(PLAYER_TYPE_AUDIO_TRACK)) {
897             runTestCubicMonotonicPlayer(
898                     "testCubicMonotonicAudioTrack", player, configuration);
899         }
900     }
901 
902     // Parameters are indices to MONOTONIC_RAMPS configuration array.
903     @Test
904     @Parameters(method = "getMonotonicRamps")
testCubicMonotonicMediaPlayerNonOffloaded( VolumeShaper.Configuration configuration)905     public void testCubicMonotonicMediaPlayerNonOffloaded(
906             VolumeShaper.Configuration configuration) throws Exception {
907         try (Player player = createPlayer(PLAYER_TYPE_MEDIA_PLAYER_NON_OFFLOADED)) {
908             runTestCubicMonotonicPlayer(
909                     "testCubicMonotonicMediaPlayerNonOffloaded", player, configuration);
910         }
911     }
912 
913     // Parameters are indices to MONOTONIC_RAMPS configuration array.
914     @Test
915     @Parameters(method = "getMonotonicRamps")
testCubicMonotonicMediaPlayerOffloaded( VolumeShaper.Configuration configuration)916     public void testCubicMonotonicMediaPlayerOffloaded(
917             VolumeShaper.Configuration configuration) throws Exception {
918         try (Player player = createPlayer(PLAYER_TYPE_MEDIA_PLAYER_OFFLOADED)) {
919             runTestCubicMonotonicPlayer(
920                     "testCubicMonotonicMediaPlayerOffloaded", player, configuration);
921         }
922     }
923 
runTestCubicMonotonicPlayer( String testName, Player player, VolumeShaper.Configuration configuration)924     private void runTestCubicMonotonicPlayer(
925             String testName, Player player, VolumeShaper.Configuration configuration)
926             throws Exception {
927         if (!hasAudioOutput()) {
928             Log.w(TAG, "AUDIO_OUTPUT feature not found. This system might not have a valid "
929                     + "audio output HAL");
930             return;
931         }
932 
933         try (VolumeShaper volumeShaper = player.createVolumeShaper(SILENCE)) {
934             volumeShaper.apply(VolumeShaper.Operation.PLAY);
935             player.start();
936             Thread.sleep(WARMUP_TIME_MS);
937 
938             // test configurations known monotonic
939             Log.d(TAG, testName + " starting test");
940 
941             float lastVolume = 0;
942             final long incrementMs = 100;
943 
944             volumeShaper.replace(configuration,
945                     VolumeShaper.Operation.PLAY, true /* join */);
946             // monotonicity test
947             for (long i = 0; i < RAMP_TIME_MS; i += incrementMs) {
948                 final float volume = volumeShaper.getVolume();
949                 assertTrue(testName + " montonic volume should increase "
950                         + volume + " >= " + lastVolume,
951                         (volume >= lastVolume));
952                 lastVolume = volume;
953                 Thread.sleep(incrementMs);
954             }
955             Thread.sleep(WARMUP_TIME_MS);
956             lastVolume = volumeShaper.getVolume();
957             assertEquals(testName
958                     + " final monotonic value should be 1.f, but is " + lastVolume,
959                     1.f, lastVolume, VOLUME_TOLERANCE);
960 
961             Log.d(TAG, "invert");
962             // invert
963             VolumeShaper.Configuration newConfiguration =
964                     new VolumeShaper.Configuration.Builder(configuration)
965                     .invertVolumes()
966                     .build();
967             volumeShaper.replace(newConfiguration,
968                     VolumeShaper.Operation.PLAY, true /* join */);
969             // monotonicity test
970             for (long i = 0; i < RAMP_TIME_MS; i += incrementMs) {
971                 final float volume = volumeShaper.getVolume();
972                 assertTrue(testName + " montonic volume should decrease "
973                         + volume + " <= " + lastVolume,
974                         (volume <= lastVolume));
975                 lastVolume = volume;
976                 Thread.sleep(incrementMs);
977             }
978             Thread.sleep(WARMUP_TIME_MS);
979             lastVolume = volumeShaper.getVolume();
980             assertEquals(testName
981                     + " final monotonic value should be 0.f, but is " + lastVolume,
982                     0.f, lastVolume, VOLUME_TOLERANCE);
983 
984             // invert + reflect
985             Log.d(TAG, "invert and reflect");
986             newConfiguration =
987                     new VolumeShaper.Configuration.Builder(configuration)
988             .invertVolumes()
989             .reflectTimes()
990             .build();
991             volumeShaper.replace(newConfiguration,
992                     VolumeShaper.Operation.PLAY, true /* join */);
993             // monotonicity test
994             for (long i = 0; i < RAMP_TIME_MS; i += incrementMs) {
995                 final float volume = volumeShaper.getVolume();
996                 assertTrue(testName + " montonic volume should increase "
997                         + volume + " >= " + lastVolume,
998                         (volume >= lastVolume - VOLUME_TOLERANCE));
999                 lastVolume = volume;
1000                 Thread.sleep(incrementMs);
1001             }
1002             Thread.sleep(WARMUP_TIME_MS);
1003             lastVolume = volumeShaper.getVolume();
1004             assertEquals(testName
1005                     + " final monotonic value should be 1.f, but is " + lastVolume,
1006                     1.f, lastVolume, VOLUME_TOLERANCE);
1007 
1008             // reflect
1009             Log.d(TAG, "reflect");
1010             newConfiguration =
1011                     new VolumeShaper.Configuration.Builder(configuration)
1012             .reflectTimes()
1013             .build();
1014             volumeShaper.replace(newConfiguration,
1015                     VolumeShaper.Operation.PLAY, true /* join */);
1016             // monotonicity test
1017             for (long i = 0; i < RAMP_TIME_MS; i += incrementMs) {
1018                 final float volume = volumeShaper.getVolume();
1019                 assertTrue(testName + " montonic volume should decrease "
1020                         + volume + " <= " + lastVolume,
1021                         (volume <= lastVolume));
1022                 lastVolume = volume;
1023                 Thread.sleep(incrementMs);
1024             }
1025             Thread.sleep(WARMUP_TIME_MS);
1026             lastVolume = volumeShaper.getVolume();
1027             assertEquals(testName
1028                     + " final monotonic value should be 0.f, but is " + lastVolume,
1029                     0.f, lastVolume, VOLUME_TOLERANCE);
1030         }
1031     } // runTestCubicMonotonicPlayer
1032 
1033     @Test
testStepRampAudioTrack()1034     public void testStepRampAudioTrack() throws Exception {
1035         try (Player player = createPlayer(PLAYER_TYPE_AUDIO_TRACK)) {
1036             runTestStepRampPlayer("testStepRampAudioTrack", player);
1037         }
1038     }
1039 
1040     @Test
testStepRampMediaPlayerNonOffloaded()1041     public void testStepRampMediaPlayerNonOffloaded() throws Exception {
1042         try (Player player = createPlayer(PLAYER_TYPE_MEDIA_PLAYER_NON_OFFLOADED)) {
1043             runTestStepRampPlayer("testStepRampMediaPlayerNonOffloaded", player);
1044         }
1045     }
1046 
1047     @Test
testStepRampMediaPlayerOffloaded()1048     public void testStepRampMediaPlayerOffloaded() throws Exception {
1049         try (Player player = createPlayer(PLAYER_TYPE_MEDIA_PLAYER_OFFLOADED)) {
1050             runTestStepRampPlayer("testStepRampMediaPlayerOffloaded", player);
1051         }
1052     }
1053 
runTestStepRampPlayer(String testName, Player player)1054     private void runTestStepRampPlayer(String testName, Player player) throws Exception {
1055         if (!hasAudioOutput()) {
1056             Log.w(TAG, "AUDIO_OUTPUT feature not found. This system might not have a valid "
1057                     + "audio output HAL");
1058             return;
1059         }
1060 
1061         // We test that the step ramp persists on value until the next control point.
1062         // The STEP_RAMP has only 2 control points (at time 0.f and at 1.f).
1063         // It should suddenly jump to full volume at 1.f (full duration).
1064         // Note: invertVolumes() and reflectTimes() are not symmetric for STEP interpolation;
1065         // however, VolumeShaper.Operation.REVERSE will behave symmetrically.
1066 
1067         try (VolumeShaper volumeShaper = player.createVolumeShaper(SILENCE)) {
1068             volumeShaper.apply(VolumeShaper.Operation.PLAY);
1069             player.start();
1070             Thread.sleep(WARMUP_TIME_MS);
1071 
1072             final VolumeShaper.Configuration configuration = STEP_RAMP;
1073             Log.d(TAG, testName + " starting test (sudden jump to full after "
1074                     + RAMP_TIME_MS + " milliseconds)");
1075 
1076             volumeShaper.replace(configuration,
1077                     VolumeShaper.Operation.PLAY, true /* join */);
1078 
1079             Thread.sleep(RAMP_TIME_MS / 2);
1080             float lastVolume = volumeShaper.getVolume();
1081             assertEquals(testName
1082                     + " middle value should be 0.f, but is " + lastVolume,
1083                     0.f, lastVolume, VOLUME_TOLERANCE);
1084 
1085             Thread.sleep(RAMP_TIME_MS / 2 + 1000);
1086             lastVolume = volumeShaper.getVolume();
1087             assertEquals(testName
1088                     + " final value should be 1.f, but is " + lastVolume,
1089                     1.f, lastVolume, VOLUME_TOLERANCE);
1090 
1091             Log.d(TAG, "invert (sudden jump to silence after "
1092                     + RAMP_TIME_MS + " milliseconds)");
1093             // invert
1094             VolumeShaper.Configuration newConfiguration =
1095                     new VolumeShaper.Configuration.Builder(configuration)
1096                         .invertVolumes()
1097                         .build();
1098             volumeShaper.replace(newConfiguration,
1099                     VolumeShaper.Operation.PLAY, true /* join */);
1100 
1101             Thread.sleep(RAMP_TIME_MS / 2);
1102             lastVolume = volumeShaper.getVolume();
1103             assertEquals(testName
1104                     + " middle value should be 1.f, but is " + lastVolume,
1105                     1.f, lastVolume, VOLUME_TOLERANCE);
1106 
1107             Thread.sleep(RAMP_TIME_MS / 2 + 1000);
1108             lastVolume = volumeShaper.getVolume();
1109             assertEquals(testName
1110                     + " final value should be 0.f, but is " + lastVolume,
1111                     0.f, lastVolume, VOLUME_TOLERANCE);
1112 
1113             // invert + reflect
1114             Log.d(TAG, "invert and reflect (sudden jump to full after "
1115                     + RAMP_TIME_MS + " milliseconds)");
1116             newConfiguration =
1117                     new VolumeShaper.Configuration.Builder(configuration)
1118                         .invertVolumes()
1119                         .reflectTimes()
1120                         .build();
1121             volumeShaper.replace(newConfiguration,
1122                     VolumeShaper.Operation.PLAY, true /* join */);
1123 
1124             Thread.sleep(RAMP_TIME_MS / 2);
1125             lastVolume = volumeShaper.getVolume();
1126             assertEquals(testName
1127                     + " middle value should be 0.f, but is " + lastVolume,
1128                     0.f, lastVolume, VOLUME_TOLERANCE);
1129 
1130             Thread.sleep(RAMP_TIME_MS / 2 + 1000);
1131             lastVolume = volumeShaper.getVolume();
1132             assertEquals(testName
1133                     + " final value should be 1.f, but is " + lastVolume,
1134                     1.f, lastVolume, VOLUME_TOLERANCE);
1135 
1136             // reflect
1137             Log.d(TAG, "reflect (sudden jump to silence after "
1138                     + RAMP_TIME_MS + " milliseconds)");
1139             newConfiguration =
1140                     new VolumeShaper.Configuration.Builder(configuration)
1141                         .reflectTimes()
1142                         .build();
1143             volumeShaper.replace(newConfiguration,
1144                     VolumeShaper.Operation.PLAY, true /* join */);
1145 
1146             Thread.sleep(RAMP_TIME_MS / 2);
1147             lastVolume = volumeShaper.getVolume();
1148             assertEquals(testName
1149                     + " middle value should be 1.f, but is " + lastVolume,
1150                     1.f, lastVolume, VOLUME_TOLERANCE);
1151 
1152             Thread.sleep(RAMP_TIME_MS / 2 + 1000);
1153             lastVolume = volumeShaper.getVolume();
1154             assertEquals(testName
1155                     + " final value should be 0.f, but is " + lastVolume,
1156                     0.f, lastVolume, VOLUME_TOLERANCE);
1157 
1158             Log.d(TAG, "reverse (immediate jump to full)");
1159             volumeShaper.apply(VolumeShaper.Operation.REVERSE);
1160             Thread.sleep(RAMP_TIME_MS / 2);
1161             lastVolume = volumeShaper.getVolume();
1162             assertEquals(testName
1163                     + " middle value should be 1.f, but is " + lastVolume,
1164                     1.f, lastVolume, VOLUME_TOLERANCE);
1165 
1166             Thread.sleep(RAMP_TIME_MS / 2 + 1000);
1167             lastVolume = volumeShaper.getVolume();
1168             assertEquals(testName
1169                     + " final value should be 1.f, but is " + lastVolume,
1170                     1.f, lastVolume, VOLUME_TOLERANCE);
1171         }
1172     } // runTestStepRampPlayer
1173 
1174     @Test
testTwoShapersAudioTrack()1175     public void testTwoShapersAudioTrack() throws Exception {
1176         try (Player player = createPlayer(PLAYER_TYPE_AUDIO_TRACK)) {
1177             runTestTwoShapersPlayer("testTwoShapersAudioTrack", player);
1178         }
1179     }
1180 
1181     @Test
testTwoShapersMediaPlayerNonOffloaded()1182     public void testTwoShapersMediaPlayerNonOffloaded() throws Exception {
1183         try (Player player = createPlayer(PLAYER_TYPE_MEDIA_PLAYER_NON_OFFLOADED)) {
1184             runTestTwoShapersPlayer("testTwoShapersMediaPlayerNonOffloaded", player);
1185         }
1186     }
1187 
1188     @Test
testTwoShapersMediaPlayerOffloaded()1189     public void testTwoShapersMediaPlayerOffloaded() throws Exception {
1190         try (Player player = createPlayer(PLAYER_TYPE_MEDIA_PLAYER_OFFLOADED)) {
1191             runTestTwoShapersPlayer("testTwoShapersMediaPlayerOffloaded", player);
1192         }
1193     }
1194 
runTestTwoShapersPlayer(String testName, Player player)1195     private void runTestTwoShapersPlayer(String testName, Player player) throws Exception {
1196 
1197         if (!hasAudioOutput()) {
1198             Log.w(TAG, "AUDIO_OUTPUT feature not found. This system might not have a valid "
1199                     + "audio output HAL");
1200             return;
1201         }
1202 
1203         final long durationMs = 10000;
1204 
1205         // Ramp configurations go from 0.f up to 1.f, Duck from 1.f to 0.f
1206         // With the two ramps combined, the audio should rise and then fall.
1207         final VolumeShaper.Configuration LONG_RAMP =
1208                 new VolumeShaper.Configuration.Builder(VolumeShaper.Configuration.LINEAR_RAMP)
1209                     .setDuration(durationMs)
1210                     .build();
1211         final VolumeShaper.Configuration LONG_DUCK =
1212                 new VolumeShaper.Configuration.Builder(LONG_RAMP)
1213                     .reflectTimes()
1214                     .build();
1215 
1216         try (
1217                 VolumeShaper volumeShaperRamp = player.createVolumeShaper(LONG_RAMP);
1218                 VolumeShaper volumeShaperDuck = player.createVolumeShaper(LONG_DUCK);
1219                 ) {
1220             final float firstVolumeRamp = volumeShaperRamp.getVolume();
1221             final float firstVolumeDuck = volumeShaperDuck.getVolume();
1222             assertEquals(testName
1223                     + " first ramp value should be 0.f, but is " + firstVolumeRamp,
1224                     0.f, firstVolumeRamp, VOLUME_TOLERANCE);
1225             assertEquals(testName
1226                     + " first duck value should be 1.f, but is " + firstVolumeDuck,
1227                     1.f, firstVolumeDuck, VOLUME_TOLERANCE);
1228             player.start();
1229 
1230             Thread.sleep(1000);
1231 
1232             final float lastVolumeRamp = volumeShaperRamp.getVolume();
1233             final float lastVolumeDuck = volumeShaperDuck.getVolume();
1234             assertEquals(testName
1235                     + " no-play ramp value should be 0.f, but is " + lastVolumeRamp,
1236                     0.f, lastVolumeRamp, VOLUME_TOLERANCE);
1237             assertEquals(testName
1238                     + " no-play duck value should be 1.f, but is " + lastVolumeDuck,
1239                     1.f, lastVolumeDuck, VOLUME_TOLERANCE);
1240 
1241             Log.d(TAG, testName + " volume should be silent and start increasing now");
1242 
1243             // we actually start now!
1244             volumeShaperRamp.apply(VolumeShaper.Operation.PLAY);
1245             volumeShaperDuck.apply(VolumeShaper.Operation.PLAY);
1246             Thread.sleep(durationMs / 2);
1247 
1248             Log.d(TAG, testName + " volume should be > 0 and about maximum here");
1249             final float lastVolumeRamp2 = volumeShaperRamp.getVolume();
1250             final float lastVolumeDuck2 = volumeShaperDuck.getVolume();
1251             assertTrue(testName
1252                     + " last ramp value should be > 0.f " + lastVolumeRamp2,
1253                     lastVolumeRamp2 > 0.f);
1254             assertTrue(testName
1255                     + " last duck value should be < 1.f " + lastVolumeDuck2,
1256                     lastVolumeDuck2 < 1.f);
1257 
1258             Log.d(TAG, testName + " volume should start decreasing shortly");
1259             Thread.sleep(durationMs / 2 + 1000);
1260 
1261             Log.d(TAG, testName + " volume should be silent now");
1262             final float lastVolumeRamp3 = volumeShaperRamp.getVolume();
1263             final float lastVolumeDuck3 = volumeShaperDuck.getVolume();
1264             assertEquals(testName
1265                     + " last ramp value should be 1.f, but is " + lastVolumeRamp3,
1266                     1.f, lastVolumeRamp3, VOLUME_TOLERANCE);
1267             assertEquals(testName
1268                     + " last duck value should be 0.f, but is " + lastVolumeDuck3,
1269                     0.f, lastVolumeDuck3, VOLUME_TOLERANCE);
1270 
1271             runCloseTest(testName, volumeShaperRamp);
1272             runCloseTest(testName, volumeShaperDuck);
1273         }
1274     } // runTestTwoShapersPlayer
1275 
1276     // tests that shaper which is based on clocktime after start (default on builder)
1277     // advances in the presence of pause and stop.
1278     @LargeTest
1279     @Test
testPlayerRunDuringPauseStop()1280     public void testPlayerRunDuringPauseStop() throws Exception {
1281         runTestPlayerDuringPauseStop("testPlayerRunDuringPauseStop",
1282                 false /* doFlush */, false /* useMediaTime */);
1283     }
1284 
1285     // tests that shaper which is based on media time will freeze
1286     // in the presence of pause and stop.
1287     @LargeTest
1288     @Test
testPlayerFreezeDuringPauseStop()1289     public void testPlayerFreezeDuringPauseStop() throws Exception {
1290         runTestPlayerDuringPauseStop("testPlayerFreezeDuringPauseStop",
1291                 false /* doFlush */, true /* useMediaTime */);
1292     }
1293 
1294     // tests that shaper which is based on media time will freeze
1295     // in the presence of pause and stop.
1296     @LargeTest
1297     @Test
testPlayerFreezeDuringPauseStopFlush()1298     public void testPlayerFreezeDuringPauseStopFlush() throws Exception {
1299         runTestPlayerDuringPauseStop("testPlayerFreezeDuringPauseStopFlush",
1300                 true /* doFlush */, true /* useMediaTime */);
1301     }
1302 
runTestPlayerDuringPauseStop( String parentTestName, boolean doFlush, boolean useMediaTime)1303     private void runTestPlayerDuringPauseStop(
1304             String parentTestName, boolean doFlush, boolean useMediaTime) throws Exception {
1305         if (!hasAudioOutput()) {
1306             Log.w(TAG, "AUDIO_OUTPUT feature not found. This system might not have a valid "
1307                     + "audio output HAL");
1308             return;
1309         }
1310 
1311         final VolumeShaper.Configuration config =
1312                 useMediaTime ? LINEAR_RAMP_MEDIA_TIME : LINEAR_RAMP;
1313 
1314         for (int p = 0; p < PLAYER_TYPES; ++p) {
1315             for (int pause = 0; pause < 2; ++pause) {
1316 
1317                 if ((p == PLAYER_TYPE_MEDIA_PLAYER_NON_OFFLOADED
1318                         || p == PLAYER_TYPE_MEDIA_PLAYER_OFFLOADED) && pause == 0) {
1319                     // Do not test stop and MediaPlayer because a
1320                     // MediaPlayer stop requires prepare before starting.
1321                     continue;
1322                 }
1323 
1324                 // Note: prior to U, offload and direct tracks used clock time
1325                 // not media time.
1326 
1327                 try (   Player player = createPlayer(p);
1328                         VolumeShaper volumeShaper = player.createVolumeShaper(config);
1329                         ) {
1330                     final String testName = parentTestName + " " + player.name();
1331 
1332                     Log.d(TAG, testName + " starting volume, should ramp up");
1333                     volumeShaper.apply(VolumeShaper.Operation.PLAY);
1334                     assertEquals(testName + " volume should be 0.f",
1335                             0.f, volumeShaper.getVolume(), VOLUME_TOLERANCE);
1336 
1337                     player.start();
1338                     Thread.sleep(WARMUP_TIME_MS * 2);
1339 
1340                     String operation = pause != 0 ? "pause" : "stop";
1341                     Log.d(TAG, testName + " applying " + operation);
1342                     if (pause == 1) {
1343                         player.pause();
1344                     } else {
1345                         player.stop();
1346                     }
1347                     if (doFlush) {
1348                         player.flush();
1349                     }
1350                     Log.d(TAG, testName + " volume right after " +
1351                             operation + " is " + volumeShaper.getVolume());
1352 
1353                     Thread.sleep(RAMP_TIME_MS);
1354 
1355                     Log.d(TAG, testName + " volume after " + operation +
1356                             " and sleep is " + volumeShaper.getVolume());
1357 
1358                     Log.d(TAG, testName + " starting again");
1359                     player.start();
1360                     Thread.sleep(WARMUP_TIME_MS * 2);
1361 
1362                     final float finalVolume = volumeShaper.getVolume();
1363                     if (useMediaTime) {
1364                         Log.d(TAG, testName + " final volume after starting should be " +
1365                                 "less than full volume, actual is " + finalVolume);
1366                         assertThat(finalVolume).isLessThan(1.f);
1367                     } else {
1368                         Log.d(TAG, testName + " final volume after starting should be " +
1369                                 "full volume, actual is " + finalVolume);
1370                         assertEquals(testName + " volume should be 1.f",
1371                                 1.f, finalVolume, VOLUME_TOLERANCE);
1372                     }
1373                 }
1374             }
1375         }
1376     } // runTestPlayerDuringPauseStop
1377 
1378     // Player should be started before calling (as it is not an argument to method).
runRampTest( String testName, VolumeShaper volumeShaper, VolumeShaper.Configuration config)1379     private void runRampTest(
1380             String testName, VolumeShaper volumeShaper, VolumeShaper.Configuration config)
1381             throws Exception {
1382         // This replaces with play.
1383         Log.d(TAG, testName + " Replace + Play (volume should increase)");
1384         volumeShaper.replace(config, VolumeShaper.Operation.PLAY, false /* join */);
1385         Thread.sleep(RAMP_TIME_MS / 2);
1386 
1387         // Reverse the direction of the volume shaper curve
1388         Log.d(TAG, testName + " Reverse (volume should decrease)");
1389         volumeShaper.apply(VolumeShaper.Operation.REVERSE);
1390         Thread.sleep(RAMP_TIME_MS / 2 + 1000);
1391 
1392         Log.d(TAG, testName + " Check Volume (silent)");
1393         assertEquals(testName + " volume should be 0.f",
1394                 0.f, volumeShaper.getVolume(), VOLUME_TOLERANCE);
1395 
1396         // Forwards
1397         Log.d(TAG, testName + " Play (volume should increase)");
1398         volumeShaper.apply(VolumeShaper.Operation.PLAY);
1399         Thread.sleep(RAMP_TIME_MS + 1000);
1400 
1401         Log.d(TAG, testName + " Check Volume (volume at max)");
1402         assertEquals(testName + " volume should be 1.f",
1403                 1.f, volumeShaper.getVolume(), VOLUME_TOLERANCE);
1404 
1405         // Reverse
1406         Log.d(TAG, testName + " Reverse (volume should decrease)");
1407         volumeShaper.apply(VolumeShaper.Operation.REVERSE);
1408         Thread.sleep(RAMP_TIME_MS + 1000);
1409 
1410         Log.d(TAG, testName + " Check Volume (volume should be silent)");
1411         assertEquals(testName + " volume should be 0.f",
1412                 0.f, volumeShaper.getVolume(), VOLUME_TOLERANCE);
1413 
1414         // Forwards
1415         Log.d(TAG, testName + " Play (volume should increase)");
1416         volumeShaper.apply(VolumeShaper.Operation.PLAY);
1417         Thread.sleep(RAMP_TIME_MS + 1000);
1418 
1419         // Comment out for headset plug/unplug test
1420         // Log.d(TAG, testName + " headset check"); Thread.sleep(10000 /* millis */);
1421         //
1422 
1423         Log.d(TAG, testName + " Check Volume (volume at max)");
1424         assertEquals(testName + " volume should be 1.f",
1425                 1.f, volumeShaper.getVolume(), VOLUME_TOLERANCE);
1426 
1427         Log.d(TAG, testName + " done");
1428     } // runRampTest
1429 
1430     // Player should be started before calling (as it is not an argument to method).
runDuckTest(String testName, VolumeShaper volumeShaper)1431     private void runDuckTest(String testName, VolumeShaper volumeShaper) throws Exception {
1432         final VolumeShaper.Configuration[] configs = new VolumeShaper.Configuration[] {
1433                 LINEAR_DUCK,
1434         };
1435 
1436         for (VolumeShaper.Configuration config : configs) {
1437             Log.d(TAG, testName + " Replace + Reverse (volume at max)");
1438             // CORNER CASE: When you replace with REVERSE, it stays at the initial point.
1439             volumeShaper.replace(config, VolumeShaper.Operation.REVERSE, false /* join */);
1440             Thread.sleep(RAMP_TIME_MS / 2);
1441             assertEquals(testName + " volume should be 1.f",
1442                     1.f, volumeShaper.getVolume(), VOLUME_TOLERANCE);
1443 
1444             // CORNER CASE: reverse twice doesn't do anything.
1445             Thread.sleep(RAMP_TIME_MS / 2);
1446             Log.d(TAG, testName + " Reverse after reverse (volume at max)");
1447             volumeShaper.apply(VolumeShaper.Operation.REVERSE);
1448             assertEquals(testName + " volume should be 1.f",
1449                     1.f, volumeShaper.getVolume(), VOLUME_TOLERANCE);
1450 
1451             Log.d(TAG, testName + " Duck from start (volume should decrease)");
1452             volumeShaper.apply(VolumeShaper.Operation.PLAY);
1453             Thread.sleep(RAMP_TIME_MS * 2);
1454 
1455             Log.d(TAG, testName + " Duck done (volume should be low, 0.2f)");
1456             assertEquals(testName + " volume should be 0.2f",
1457                     0.2f, volumeShaper.getVolume(), VOLUME_TOLERANCE);
1458 
1459             Log.d(TAG, testName + " Unduck (volume should increase)");
1460             volumeShaper.apply(VolumeShaper.Operation.REVERSE);
1461             Thread.sleep(RAMP_TIME_MS * 2);
1462 
1463             // Comment out for headset plug/unplug test
1464             // Log.d(TAG, testName + " headset check"); Thread.sleep(10000 /* millis */);
1465             //
1466             Log.d(TAG, testName + " Unduck done (volume at max)");
1467             assertEquals(testName + " volume should be 1.f",
1468                     1.f, volumeShaper.getVolume(), VOLUME_TOLERANCE);
1469         }
1470     } // runDuckTest
1471 
1472     // VolumeShaper should not be started prior to this test; it is replaced
1473     // in this test.
runRampCornerCaseTest( String testName, VolumeShaper volumeShaper, VolumeShaper.Configuration config)1474     private void runRampCornerCaseTest(
1475             String testName, VolumeShaper volumeShaper, VolumeShaper.Configuration config)
1476                     throws Exception {
1477         // ramps start at 0.f
1478         assertEquals(testName + " volume should be 0.f",
1479                 0.f, volumeShaper.getVolume(), VOLUME_TOLERANCE);
1480 
1481         Log.d(TAG, testName + " Reverse at start (quiet now)");
1482         // CORNER CASE: When you begin with REVERSE, it stays at the initial point.
1483         volumeShaper.apply(VolumeShaper.Operation.REVERSE);
1484         Thread.sleep(RAMP_TIME_MS / 2);
1485         assertEquals(testName + " volume should be 0.f",
1486                 0.f, volumeShaper.getVolume(), VOLUME_TOLERANCE);
1487 
1488         // CORNER CASE: reverse twice doesn't do anything.
1489         Thread.sleep(RAMP_TIME_MS / 2);
1490         Log.d(TAG, testName + " Reverse after reverse (still quiet)");
1491         volumeShaper.apply(VolumeShaper.Operation.REVERSE);
1492         assertEquals(testName + " volume should be 0.f",
1493                 0.f, volumeShaper.getVolume(), VOLUME_TOLERANCE);
1494 
1495         Log.d(TAG, testName + " Ramp from start (volume should increase)");
1496         volumeShaper.apply(VolumeShaper.Operation.PLAY);
1497         Thread.sleep(RAMP_TIME_MS * 2);
1498 
1499         Log.d(TAG, testName + " Volume persists at maximum 1.f");
1500         assertEquals(testName + " volume should be 1.f",
1501                 1.f, volumeShaper.getVolume(), VOLUME_TOLERANCE);
1502 
1503         Log.d(TAG, testName + " Reverse ramp (volume should decrease)");
1504         volumeShaper.apply(VolumeShaper.Operation.REVERSE);
1505         Thread.sleep(RAMP_TIME_MS / 2);
1506 
1507         // join in REVERSE should freeze
1508         final float volume = volumeShaper.getVolume();
1509         Log.d(TAG, testName + " Replace ramp with join in REVERSE (volume steady)");
1510         volumeShaper.replace(config, VolumeShaper.Operation.REVERSE, true /* join */);
1511 
1512         Thread.sleep(RAMP_TIME_MS / 2);
1513         // Are we frozen?
1514         final float volume2 = volumeShaper.getVolume();
1515         assertEquals(testName + " volume should be the same (volume steady)",
1516                 volume, volume2, JOIN_VOLUME_TOLERANCE);
1517 
1518         // Begin playing
1519         Log.d(TAG, testName + " Play joined ramp (volume should increase)");
1520         volumeShaper.apply(VolumeShaper.Operation.PLAY);
1521         Thread.sleep(RAMP_TIME_MS * 2);
1522 
1523         // Reverse to get back to start of the joined curve.
1524         Log.d(TAG, testName + " Reverse joined ramp (volume should decrease)");
1525         volumeShaper.apply(VolumeShaper.Operation.REVERSE);
1526         Thread.sleep(RAMP_TIME_MS * 2);
1527 
1528         // At this time, we are back to the join point.
1529         // We check now that the scaling for the join is permanent.
1530         Log.d(TAG, testName + " Joined ramp at start (volume same as at join)");
1531         final float volume3 = volumeShaper.getVolume();
1532         assertEquals(testName + " volume should be same as start for joined ramp",
1533                 volume2, volume3, JOIN_VOLUME_TOLERANCE);
1534     } // runRampCornerCaseTest
1535 
1536     // volumeShaper is closed in this test.
runCloseTest(String testName, VolumeShaper volumeShaper)1537     private void runCloseTest(String testName, VolumeShaper volumeShaper) throws Exception {
1538         Log.d(TAG, testName + " closing");
1539         volumeShaper.close();
1540         runCloseTest2(testName, volumeShaper);
1541     } // runCloseTest
1542 
1543     // VolumeShaper should be closed prior to this test.
runCloseTest2(String testName, VolumeShaper volumeShaper)1544     private void runCloseTest2(String testName, VolumeShaper volumeShaper) throws Exception {
1545         // CORNER CASE:
1546         // VolumeShaper methods should throw ISE after closing.
1547         Log.d(TAG, testName + " getVolume() after close should throw ISE");
1548         assertThrows(IllegalStateException.class,
1549                 volumeShaper::getVolume);
1550 
1551         Log.d(TAG, testName + " apply() after close should throw ISE");
1552         assertThrows(IllegalStateException.class,
1553                 ()->{ volumeShaper.apply(VolumeShaper.Operation.REVERSE); });
1554 
1555         Log.d(TAG, testName + " replace() after close should throw ISE");
1556         assertThrows(IllegalStateException.class,
1557                 ()->{ volumeShaper.replace(
1558                         LINEAR_RAMP, VolumeShaper.Operation.PLAY, false /* join */); });
1559 
1560         Log.d(TAG, testName + " closing x2 is OK");
1561         volumeShaper.close(); // OK to close twice.
1562         Log.d(TAG, testName + " closing x3 is OK");
1563         volumeShaper.close(); // OK to close thrice.
1564     } // runCloseTest2
1565 
1566     // Player should not be started prior to calling (it is started in this test)
1567     // VolumeShaper should not be started prior to calling (it is not started in this test).
runStartIdleTest(String testName, VolumeShaper volumeShaper, Player player)1568     private void runStartIdleTest(String testName, VolumeShaper volumeShaper, Player player)
1569             throws Exception {
1570         Log.d(TAG, testName + " volume after creation or pause doesn't advance (silent now)");
1571         // CORNER CASE:
1572         // volumeShaper volume after creation or pause doesn't advance.
1573         Thread.sleep(WARMUP_TIME_MS);
1574         assertEquals(testName + " volume should be 0.f",
1575                 0.f, volumeShaper.getVolume(), VOLUME_TOLERANCE);
1576 
1577         player.start();
1578         Thread.sleep(WARMUP_TIME_MS);
1579 
1580         Log.d(TAG, testName + " volume after player start doesn't advance if play isn't called."
1581                 + " (still silent)");
1582         // CORNER CASE:
1583         // volumeShaper volume after creation doesn't or pause doesn't advance even
1584         // after the player starts.
1585         Thread.sleep(WARMUP_TIME_MS);
1586         assertEquals(testName + " volume should be 0.f",
1587                 0.f, volumeShaper.getVolume(), VOLUME_TOLERANCE);
1588     } // runStartIdleTest
1589 
1590     // Player should not be running prior to calling (it is started in this test).
1591     // VolumeShaper is also started in this test.
runStartSyncTest(String testName, VolumeShaper volumeShaper, Player player)1592     private void runStartSyncTest(String testName, VolumeShaper volumeShaper, Player player)
1593             throws Exception {
1594         Log.d(TAG, testName + " volume after creation or pause doesn't advance "
1595                 + "if player isn't started. (silent now)");
1596         volumeShaper.apply(VolumeShaper.Operation.PLAY);
1597         // CORNER CASE:
1598         // volumeShaper volume after creation or pause doesn't advance
1599         // even after play is called.
1600         Thread.sleep(WARMUP_TIME_MS);
1601         assertEquals(testName + " volume should be 0.f",
1602                 0.f, volumeShaper.getVolume(), VOLUME_TOLERANCE);
1603 
1604         Log.d(TAG, testName + " starting now (volume should increase)");
1605         player.start();
1606         Thread.sleep(WARMUP_TIME_MS);
1607 
1608         Log.d(TAG, testName + " volume after player start advances if play is called.");
1609         // CORNER CASE:
1610         // Now volume should have advanced since play is called.
1611         Thread.sleep(WARMUP_TIME_MS);
1612         assertTrue(testName + " volume should be greater than 0.f",
1613                 volumeShaper.getVolume() > 0.f);
1614     } // runStartSyncTest
1615 
getContext()1616     private static Context getContext() {
1617         return InstrumentationRegistry.getInstrumentation().getTargetContext();
1618     }
1619 }
1620