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