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