Home | History | Annotate | Download | only in media
      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 package android.media;
     17 
     18 import android.annotation.IntDef;
     19 import android.annotation.NonNull;
     20 import android.annotation.Nullable;
     21 import android.os.Parcel;
     22 import android.os.Parcelable;
     23 
     24 import java.lang.annotation.Retention;
     25 import java.lang.annotation.RetentionPolicy;
     26 import java.lang.AutoCloseable;
     27 import java.lang.ref.WeakReference;
     28 import java.util.Arrays;
     29 import java.util.Objects;
     30 
     31 /**
     32  * The {@code VolumeShaper} class is used to automatically control audio volume during media
     33  * playback, allowing simple implementation of transition effects and ducking.
     34  * It is created from implementations of {@code VolumeAutomation},
     35  * such as {@code MediaPlayer} and {@code AudioTrack} (referred to as "players" below),
     36  * by {@link MediaPlayer#createVolumeShaper} or {@link AudioTrack#createVolumeShaper}.
     37  *
     38  * A {@code VolumeShaper} is intended for short volume changes.
     39  * If the audio output sink changes during
     40  * a {@code VolumeShaper} transition, the precise curve position may be lost, and the
     41  * {@code VolumeShaper} may advance to the end of the curve for the new audio output sink.
     42  *
     43  * The {@code VolumeShaper} appears as an additional scaling on the audio output,
     44  * and adjusts independently of track or stream volume controls.
     45  */
     46 public final class VolumeShaper implements AutoCloseable {
     47     /* member variables */
     48     private int mId;
     49     private final WeakReference<PlayerBase> mWeakPlayerBase;
     50 
     51     /* package */ VolumeShaper(
     52             @NonNull Configuration configuration, @NonNull PlayerBase playerBase) {
     53         mWeakPlayerBase = new WeakReference<PlayerBase>(playerBase);
     54         mId = applyPlayer(configuration, new Operation.Builder().defer().build());
     55     }
     56 
     57     /* package */ int getId() {
     58         return mId;
     59     }
     60 
     61     /**
     62      * Applies the {@link VolumeShaper.Operation} to the {@code VolumeShaper}.
     63      *
     64      * Applying {@link VolumeShaper.Operation#PLAY} after {@code PLAY}
     65      * or {@link VolumeShaper.Operation#REVERSE} after
     66      * {@code REVERSE} has no effect.
     67      *
     68      * Applying {@link VolumeShaper.Operation#PLAY} when the player
     69      * hasn't started will synchronously start the {@code VolumeShaper} when
     70      * playback begins.
     71      *
     72      * @param operation the {@code operation} to apply.
     73      * @throws IllegalStateException if the player is uninitialized or if there
     74      *         is a critical failure. In that case, the {@code VolumeShaper} should be
     75      *         recreated.
     76      */
     77     public void apply(@NonNull Operation operation) {
     78         /* void */ applyPlayer(new VolumeShaper.Configuration(mId), operation);
     79     }
     80 
     81     /**
     82      * Replaces the current {@code VolumeShaper}
     83      * {@code configuration} with a new {@code configuration}.
     84      *
     85      * This allows the user to change the volume shape
     86      * while the existing {@code VolumeShaper} is in effect.
     87      *
     88      * The effect of {@code replace()} is similar to an atomic close of
     89      * the existing {@code VolumeShaper} and creation of a new {@code VolumeShaper}.
     90      *
     91      * If the {@code operation} is {@link VolumeShaper.Operation#PLAY} then the
     92      * new curve starts immediately.
     93      *
     94      * If the {@code operation} is
     95      * {@link VolumeShaper.Operation#REVERSE}, then the new curve will
     96      * be delayed until {@code PLAY} is applied.
     97      *
     98      * @param configuration the new {@code configuration} to use.
     99      * @param operation the {@code operation} to apply to the {@code VolumeShaper}
    100      * @param join if true, match the start volume of the
    101      *             new {@code configuration} to the current volume of the existing
    102      *             {@code VolumeShaper}, to avoid discontinuity.
    103      * @throws IllegalStateException if the player is uninitialized or if there
    104      *         is a critical failure. In that case, the {@code VolumeShaper} should be
    105      *         recreated.
    106      */
    107     public void replace(
    108             @NonNull Configuration configuration, @NonNull Operation operation, boolean join) {
    109         mId = applyPlayer(
    110                 configuration,
    111                 new Operation.Builder(operation).replace(mId, join).build());
    112     }
    113 
    114     /**
    115      * Returns the current volume scale attributable to the {@code VolumeShaper}.
    116      *
    117      * This is the last volume from the {@code VolumeShaper} used for the player,
    118      * or the initial volume if the {@code VolumeShaper} hasn't been started with
    119      * {@link VolumeShaper.Operation#PLAY}.
    120      *
    121      * @return the volume, linearly represented as a value between 0.f and 1.f.
    122      * @throws IllegalStateException if the player is uninitialized or if there
    123      *         is a critical failure.  In that case, the {@code VolumeShaper} should be
    124      *         recreated.
    125      */
    126     public float getVolume() {
    127         return getStatePlayer(mId).getVolume();
    128     }
    129 
    130     /**
    131      * Releases the {@code VolumeShaper} object; any volume scale due to the
    132      * {@code VolumeShaper} is removed after closing.
    133      *
    134      * If the volume does not reach 1.f when the {@code VolumeShaper} is closed
    135      * (or finalized), there may be an abrupt change of volume.
    136      *
    137      * {@code close()} may be safely called after a prior {@code close()}.
    138      * This class implements the Java {@code AutoClosable} interface and
    139      * may be used with try-with-resources.
    140      */
    141     @Override
    142     public void close() {
    143         try {
    144             /* void */ applyPlayer(
    145                     new VolumeShaper.Configuration(mId),
    146                     new Operation.Builder().terminate().build());
    147         } catch (IllegalStateException ise) {
    148             ; // ok
    149         }
    150         if (mWeakPlayerBase != null) {
    151             mWeakPlayerBase.clear();
    152         }
    153     }
    154 
    155     @Override
    156     protected void finalize() {
    157         close(); // ensure we remove the native VolumeShaper
    158     }
    159 
    160     /**
    161      * Internal call to apply the {@code configuration} and {@code operation} to the player.
    162      * Returns a valid shaper id or throws the appropriate exception.
    163      * @param configuration
    164      * @param operation
    165      * @return id a non-negative shaper id.
    166      * @throws IllegalStateException if the player has been deallocated or is uninitialized.
    167      */
    168     private int applyPlayer(
    169             @NonNull VolumeShaper.Configuration configuration,
    170             @NonNull VolumeShaper.Operation operation) {
    171         final int id;
    172         if (mWeakPlayerBase != null) {
    173             PlayerBase player = mWeakPlayerBase.get();
    174             if (player == null) {
    175                 throw new IllegalStateException("player deallocated");
    176             }
    177             id = player.playerApplyVolumeShaper(configuration, operation);
    178         } else {
    179             throw new IllegalStateException("uninitialized shaper");
    180         }
    181         if (id < 0) {
    182             // TODO - get INVALID_OPERATION from platform.
    183             final int VOLUME_SHAPER_INVALID_OPERATION = -38; // must match with platform
    184             // Due to RPC handling, we translate integer codes to exceptions right before
    185             // delivering to the user.
    186             if (id == VOLUME_SHAPER_INVALID_OPERATION) {
    187                 throw new IllegalStateException("player or VolumeShaper deallocated");
    188             } else {
    189                 throw new IllegalArgumentException("invalid configuration or operation: " + id);
    190             }
    191         }
    192         return id;
    193     }
    194 
    195     /**
    196      * Internal call to retrieve the current {@code VolumeShaper} state.
    197      * @param id
    198      * @return the current {@code VolumeShaper.State}
    199      * @throws IllegalStateException if the player has been deallocated or is uninitialized.
    200      */
    201     private @NonNull VolumeShaper.State getStatePlayer(int id) {
    202         final VolumeShaper.State state;
    203         if (mWeakPlayerBase != null) {
    204             PlayerBase player = mWeakPlayerBase.get();
    205             if (player == null) {
    206                 throw new IllegalStateException("player deallocated");
    207             }
    208             state = player.playerGetVolumeShaperState(id);
    209         } else {
    210             throw new IllegalStateException("uninitialized shaper");
    211         }
    212         if (state == null) {
    213             throw new IllegalStateException("shaper cannot be found");
    214         }
    215         return state;
    216     }
    217 
    218     /**
    219      * The {@code VolumeShaper.Configuration} class contains curve
    220      * and duration information.
    221      * It is constructed by the {@link VolumeShaper.Configuration.Builder}.
    222      * <p>
    223      * A {@code VolumeShaper.Configuration} is used by
    224      * {@link VolumeAutomation#createVolumeShaper(Configuration)
    225      * VolumeAutomation.createVolumeShaper(Configuration)} to create
    226      * a {@code VolumeShaper} and
    227      * by {@link VolumeShaper#replace(Configuration, Operation, boolean)
    228      * VolumeShaper.replace(Configuration, Operation, boolean)}
    229      * to replace an existing {@code configuration}.
    230      * <p>
    231      * The {@link AudioTrack} and {@link MediaPlayer} classes implement
    232      * the {@link VolumeAutomation} interface.
    233      */
    234     public static final class Configuration implements Parcelable {
    235         private static final int MAXIMUM_CURVE_POINTS = 16;
    236 
    237         /**
    238          * Returns the maximum number of curve points allowed for
    239          * {@link VolumeShaper.Builder#setCurve(float[], float[])}.
    240          */
    241         public static int getMaximumCurvePoints() {
    242             return MAXIMUM_CURVE_POINTS;
    243         }
    244 
    245         // These values must match the native VolumeShaper::Configuration::Type
    246         /** @hide */
    247         @IntDef({
    248             TYPE_ID,
    249             TYPE_SCALE,
    250             })
    251         @Retention(RetentionPolicy.SOURCE)
    252         public @interface Type {}
    253 
    254         /**
    255          * Specifies a {@link VolumeShaper} handle created by {@link #VolumeShaper(int)}
    256          * from an id returned by {@code setVolumeShaper()}.
    257          * The type, curve, etc. may not be queried from
    258          * a {@code VolumeShaper} object of this type;
    259          * the handle is used to identify and change the operation of
    260          * an existing {@code VolumeShaper} sent to the player.
    261          */
    262         /* package */ static final int TYPE_ID = 0;
    263 
    264         /**
    265          * Specifies a {@link VolumeShaper} to be used
    266          * as an additional scale to the current volume.
    267          * This is created by the {@link VolumeShaper.Builder}.
    268          */
    269         /* package */ static final int TYPE_SCALE = 1;
    270 
    271         // These values must match the native InterpolatorType enumeration.
    272         /** @hide */
    273         @IntDef({
    274             INTERPOLATOR_TYPE_STEP,
    275             INTERPOLATOR_TYPE_LINEAR,
    276             INTERPOLATOR_TYPE_CUBIC,
    277             INTERPOLATOR_TYPE_CUBIC_MONOTONIC,
    278             })
    279         @Retention(RetentionPolicy.SOURCE)
    280         public @interface InterpolatorType {}
    281 
    282         /**
    283          * Stepwise volume curve.
    284          */
    285         public static final int INTERPOLATOR_TYPE_STEP = 0;
    286 
    287         /**
    288          * Linear interpolated volume curve.
    289          */
    290         public static final int INTERPOLATOR_TYPE_LINEAR = 1;
    291 
    292         /**
    293          * Cubic interpolated volume curve.
    294          * This is default if unspecified.
    295          */
    296         public static final int INTERPOLATOR_TYPE_CUBIC = 2;
    297 
    298         /**
    299          * Cubic interpolated volume curve
    300          * that preserves local monotonicity.
    301          * So long as the control points are locally monotonic,
    302          * the curve interpolation between those points are monotonic.
    303          * This is useful for cubic spline interpolated
    304          * volume ramps and ducks.
    305          */
    306         public static final int INTERPOLATOR_TYPE_CUBIC_MONOTONIC = 3;
    307 
    308         // These values must match the native VolumeShaper::Configuration::InterpolatorType
    309         /** @hide */
    310         @IntDef({
    311             OPTION_FLAG_VOLUME_IN_DBFS,
    312             OPTION_FLAG_CLOCK_TIME,
    313             })
    314         @Retention(RetentionPolicy.SOURCE)
    315         public @interface OptionFlag {}
    316 
    317         /**
    318          * @hide
    319          * Use a dB full scale volume range for the volume curve.
    320          *<p>
    321          * The volume scale is typically from 0.f to 1.f on a linear scale;
    322          * this option changes to -inf to 0.f on a db full scale,
    323          * where 0.f is equivalent to a scale of 1.f.
    324          */
    325         public static final int OPTION_FLAG_VOLUME_IN_DBFS = (1 << 0);
    326 
    327         /**
    328          * @hide
    329          * Use clock time instead of media time.
    330          *<p>
    331          * The default implementation of {@code VolumeShaper} is to apply
    332          * volume changes by the media time of the player.
    333          * Hence, the {@code VolumeShaper} will speed or slow down to
    334          * match player changes of playback rate, pause, or resume.
    335          *<p>
    336          * The {@code OPTION_FLAG_CLOCK_TIME} option allows the {@code VolumeShaper}
    337          * progress to be determined by clock time instead of media time.
    338          */
    339         public static final int OPTION_FLAG_CLOCK_TIME = (1 << 1);
    340 
    341         private static final int OPTION_FLAG_PUBLIC_ALL =
    342                 OPTION_FLAG_VOLUME_IN_DBFS | OPTION_FLAG_CLOCK_TIME;
    343 
    344         /**
    345          * A one second linear ramp from silence to full volume.
    346          * Use {@link VolumeShaper.Builder#reflectTimes()}
    347          * or {@link VolumeShaper.Builder#invertVolumes()} to generate
    348          * the matching linear duck.
    349          */
    350         public static final Configuration LINEAR_RAMP = new VolumeShaper.Configuration.Builder()
    351                 .setInterpolatorType(INTERPOLATOR_TYPE_LINEAR)
    352                 .setCurve(new float[] {0.f, 1.f} /* times */,
    353                         new float[] {0.f, 1.f} /* volumes */)
    354                 .setDuration(1000)
    355                 .build();
    356 
    357         /**
    358          * A one second cubic ramp from silence to full volume.
    359          * Use {@link VolumeShaper.Builder#reflectTimes()}
    360          * or {@link VolumeShaper.Builder#invertVolumes()} to generate
    361          * the matching cubic duck.
    362          */
    363         public static final Configuration CUBIC_RAMP = new VolumeShaper.Configuration.Builder()
    364                 .setInterpolatorType(INTERPOLATOR_TYPE_CUBIC)
    365                 .setCurve(new float[] {0.f, 1.f} /* times */,
    366                         new float[] {0.f, 1.f}  /* volumes */)
    367                 .setDuration(1000)
    368                 .build();
    369 
    370         /**
    371          * A one second sine curve
    372          * from silence to full volume for energy preserving cross fades.
    373          * Use {@link VolumeShaper.Builder#reflectTimes()} to generate
    374          * the matching cosine duck.
    375          */
    376         public static final Configuration SINE_RAMP;
    377 
    378         /**
    379          * A one second sine-squared s-curve ramp
    380          * from silence to full volume.
    381          * Use {@link VolumeShaper.Builder#reflectTimes()}
    382          * or {@link VolumeShaper.Builder#invertVolumes()} to generate
    383          * the matching sine-squared s-curve duck.
    384          */
    385         public static final Configuration SCURVE_RAMP;
    386 
    387         static {
    388             final int POINTS = MAXIMUM_CURVE_POINTS;
    389             final float times[] = new float[POINTS];
    390             final float sines[] = new float[POINTS];
    391             final float scurve[] = new float[POINTS];
    392             for (int i = 0; i < POINTS; ++i) {
    393                 times[i] = (float)i / (POINTS - 1);
    394                 final float sine = (float)Math.sin(times[i] * Math.PI / 2.);
    395                 sines[i] = sine;
    396                 scurve[i] = sine * sine;
    397             }
    398             SINE_RAMP = new VolumeShaper.Configuration.Builder()
    399                 .setInterpolatorType(INTERPOLATOR_TYPE_CUBIC)
    400                 .setCurve(times, sines)
    401                 .setDuration(1000)
    402                 .build();
    403             SCURVE_RAMP = new VolumeShaper.Configuration.Builder()
    404                 .setInterpolatorType(INTERPOLATOR_TYPE_CUBIC)
    405                 .setCurve(times, scurve)
    406                 .setDuration(1000)
    407                 .build();
    408         }
    409 
    410         /*
    411          * member variables - these are all final
    412          */
    413 
    414         // type of VolumeShaper
    415         private final int mType;
    416 
    417         // valid when mType is TYPE_ID
    418         private final int mId;
    419 
    420         // valid when mType is TYPE_SCALE
    421         private final int mOptionFlags;
    422         private final double mDurationMs;
    423         private final int mInterpolatorType;
    424         private final float[] mTimes;
    425         private final float[] mVolumes;
    426 
    427         @Override
    428         public String toString() {
    429             return "VolumeShaper.Configuration{"
    430                     + "mType = " + mType
    431                     + ", mId = " + mId
    432                     + (mType == TYPE_ID
    433                         ? "}"
    434                         : ", mOptionFlags = 0x" + Integer.toHexString(mOptionFlags).toUpperCase()
    435                         + ", mDurationMs = " + mDurationMs
    436                         + ", mInterpolatorType = " + mInterpolatorType
    437                         + ", mTimes[] = " + Arrays.toString(mTimes)
    438                         + ", mVolumes[] = " + Arrays.toString(mVolumes)
    439                         + "}");
    440         }
    441 
    442         @Override
    443         public int hashCode() {
    444             return mType == TYPE_ID
    445                     ? Objects.hash(mType, mId)
    446                     : Objects.hash(mType, mId,
    447                             mOptionFlags, mDurationMs, mInterpolatorType,
    448                             Arrays.hashCode(mTimes), Arrays.hashCode(mVolumes));
    449         }
    450 
    451         @Override
    452         public boolean equals(Object o) {
    453             if (!(o instanceof Configuration)) return false;
    454             if (o == this) return true;
    455             final Configuration other = (Configuration) o;
    456             // Note that exact floating point equality may not be guaranteed
    457             // for a theoretically idempotent operation; for example,
    458             // there are many cases where a + b - b != a.
    459             return mType == other.mType
    460                     && mId == other.mId
    461                     && (mType == TYPE_ID
    462                         ||  (mOptionFlags == other.mOptionFlags
    463                             && mDurationMs == other.mDurationMs
    464                             && mInterpolatorType == other.mInterpolatorType
    465                             && Arrays.equals(mTimes, other.mTimes)
    466                             && Arrays.equals(mVolumes, other.mVolumes)));
    467         }
    468 
    469         @Override
    470         public int describeContents() {
    471             return 0;
    472         }
    473 
    474         @Override
    475         public void writeToParcel(Parcel dest, int flags) {
    476             // this needs to match the native VolumeShaper.Configuration parceling
    477             dest.writeInt(mType);
    478             dest.writeInt(mId);
    479             if (mType != TYPE_ID) {
    480                 dest.writeInt(mOptionFlags);
    481                 dest.writeDouble(mDurationMs);
    482                 // this needs to match the native Interpolator parceling
    483                 dest.writeInt(mInterpolatorType);
    484                 dest.writeFloat(0.f); // first slope (specifying for native side)
    485                 dest.writeFloat(0.f); // last slope (specifying for native side)
    486                 // mTimes and mVolumes should have the same length.
    487                 dest.writeInt(mTimes.length);
    488                 for (int i = 0; i < mTimes.length; ++i) {
    489                     dest.writeFloat(mTimes[i]);
    490                     dest.writeFloat(mVolumes[i]);
    491                 }
    492             }
    493         }
    494 
    495         public static final Parcelable.Creator<VolumeShaper.Configuration> CREATOR
    496                 = new Parcelable.Creator<VolumeShaper.Configuration>() {
    497             @Override
    498             public VolumeShaper.Configuration createFromParcel(Parcel p) {
    499                 // this needs to match the native VolumeShaper.Configuration parceling
    500                 final int type = p.readInt();
    501                 final int id = p.readInt();
    502                 if (type == TYPE_ID) {
    503                     return new VolumeShaper.Configuration(id);
    504                 } else {
    505                     final int optionFlags = p.readInt();
    506                     final double durationMs = p.readDouble();
    507                     // this needs to match the native Interpolator parceling
    508                     final int interpolatorType = p.readInt();
    509                     final float firstSlope = p.readFloat(); // ignored on the Java side
    510                     final float lastSlope = p.readFloat();  // ignored on the Java side
    511                     final int length = p.readInt();
    512                     final float[] times = new float[length];
    513                     final float[] volumes = new float[length];
    514                     for (int i = 0; i < length; ++i) {
    515                         times[i] = p.readFloat();
    516                         volumes[i] = p.readFloat();
    517                     }
    518 
    519                     return new VolumeShaper.Configuration(
    520                         type,
    521                         id,
    522                         optionFlags,
    523                         durationMs,
    524                         interpolatorType,
    525                         times,
    526                         volumes);
    527                 }
    528             }
    529 
    530             @Override
    531             public VolumeShaper.Configuration[] newArray(int size) {
    532                 return new VolumeShaper.Configuration[size];
    533             }
    534         };
    535 
    536         /**
    537          * @hide
    538          * Constructs a {@code VolumeShaper} from an id.
    539          *
    540          * This is an opaque handle for controlling a {@code VolumeShaper} that has
    541          * already been sent to a player.  The {@code id} is returned from the
    542          * initial {@code setVolumeShaper()} call on success.
    543          *
    544          * These configurations are for native use only,
    545          * they are never returned directly to the user.
    546          *
    547          * @param id
    548          * @throws IllegalArgumentException if id is negative.
    549          */
    550         public Configuration(int id) {
    551             if (id < 0) {
    552                 throw new IllegalArgumentException("negative id " + id);
    553             }
    554             mType = TYPE_ID;
    555             mId = id;
    556             mInterpolatorType = 0;
    557             mOptionFlags = 0;
    558             mDurationMs = 0;
    559             mTimes = null;
    560             mVolumes = null;
    561         }
    562 
    563         /**
    564          * Direct constructor for VolumeShaper.
    565          * Use the Builder instead.
    566          */
    567         private Configuration(@Type int type,
    568                 int id,
    569                 @OptionFlag int optionFlags,
    570                 double durationMs,
    571                 @InterpolatorType int interpolatorType,
    572                 @NonNull float[] times,
    573                 @NonNull float[] volumes) {
    574             mType = type;
    575             mId = id;
    576             mOptionFlags = optionFlags;
    577             mDurationMs = durationMs;
    578             mInterpolatorType = interpolatorType;
    579             // Builder should have cloned these arrays already.
    580             mTimes = times;
    581             mVolumes = volumes;
    582         }
    583 
    584         /**
    585          * @hide
    586          * Returns the {@code VolumeShaper} type.
    587          */
    588         public @Type int getType() {
    589             return mType;
    590         }
    591 
    592         /**
    593          * @hide
    594          * Returns the {@code VolumeShaper} id.
    595          */
    596         public int getId() {
    597             return mId;
    598         }
    599 
    600         /**
    601          * Returns the interpolator type.
    602          */
    603         public @InterpolatorType int getInterpolatorType() {
    604             return mInterpolatorType;
    605         }
    606 
    607         /**
    608          * @hide
    609          * Returns the option flags
    610          */
    611         public @OptionFlag int getOptionFlags() {
    612             return mOptionFlags & OPTION_FLAG_PUBLIC_ALL;
    613         }
    614 
    615         /* package */ @OptionFlag int getAllOptionFlags() {
    616             return mOptionFlags;
    617         }
    618 
    619         /**
    620          * Returns the duration of the volume shape in milliseconds.
    621          */
    622         public long getDuration() {
    623             // casting is safe here as the duration was set as a long in the Builder
    624             return (long) mDurationMs;
    625         }
    626 
    627         /**
    628          * Returns the times (x) coordinate array of the volume curve points.
    629          */
    630         public float[] getTimes() {
    631             return mTimes;
    632         }
    633 
    634         /**
    635          * Returns the volumes (y) coordinate array of the volume curve points.
    636          */
    637         public float[] getVolumes() {
    638             return mVolumes;
    639         }
    640 
    641         /**
    642          * Checks the validity of times and volumes point representation.
    643          *
    644          * {@code times[]} and {@code volumes[]} are two arrays representing points
    645          * for the volume curve.
    646          *
    647          * Note that {@code times[]} and {@code volumes[]} are explicitly checked against
    648          * null here to provide the proper error string - those are legitimate
    649          * arguments to this method.
    650          *
    651          * @param times the x coordinates for the points,
    652          *        must be between 0.f and 1.f and be monotonic.
    653          * @param volumes the y coordinates for the points,
    654          *        must be between 0.f and 1.f for linear and
    655          *        must be no greater than 0.f for log (dBFS).
    656          * @param log set to true if the scale is logarithmic.
    657          * @return null if no error, or the reason in a {@code String} for an error.
    658          */
    659         private static @Nullable String checkCurveForErrors(
    660                 @Nullable float[] times, @Nullable float[] volumes, boolean log) {
    661             if (times == null) {
    662                 return "times array must be non-null";
    663             } else if (volumes == null) {
    664                 return "volumes array must be non-null";
    665             } else if (times.length != volumes.length) {
    666                 return "array length must match";
    667             } else if (times.length < 2) {
    668                 return "array length must be at least 2";
    669             } else if (times.length > MAXIMUM_CURVE_POINTS) {
    670                 return "array length must be no larger than " + MAXIMUM_CURVE_POINTS;
    671             } else if (times[0] != 0.f) {
    672                 return "times must start at 0.f";
    673             } else if (times[times.length - 1] != 1.f) {
    674                 return "times must end at 1.f";
    675             }
    676 
    677             // validate points along the curve
    678             for (int i = 1; i < times.length; ++i) {
    679                 if (!(times[i] > times[i - 1]) /* handle nan */) {
    680                     return "times not monotonic increasing, check index " + i;
    681                 }
    682             }
    683             if (log) {
    684                 for (int i = 0; i < volumes.length; ++i) {
    685                     if (!(volumes[i] <= 0.f) /* handle nan */) {
    686                         return "volumes for log scale cannot be positive, "
    687                                 + "check index " + i;
    688                     }
    689                 }
    690             } else {
    691                 for (int i = 0; i < volumes.length; ++i) {
    692                     if (!(volumes[i] >= 0.f) || !(volumes[i] <= 1.f) /* handle nan */) {
    693                         return "volumes for linear scale must be between 0.f and 1.f, "
    694                                 + "check index " + i;
    695                     }
    696                 }
    697             }
    698             return null; // no errors
    699         }
    700 
    701         private static void checkCurveForErrorsAndThrowException(
    702                 @Nullable float[] times, @Nullable float[] volumes, boolean log, boolean ise) {
    703             final String error = checkCurveForErrors(times, volumes, log);
    704             if (error != null) {
    705                 if (ise) {
    706                     throw new IllegalStateException(error);
    707                 } else {
    708                     throw new IllegalArgumentException(error);
    709                 }
    710             }
    711         }
    712 
    713         private static void checkValidVolumeAndThrowException(float volume, boolean log) {
    714             if (log) {
    715                 if (!(volume <= 0.f) /* handle nan */) {
    716                     throw new IllegalArgumentException("dbfs volume must be 0.f or less");
    717                 }
    718             } else {
    719                 if (!(volume >= 0.f) || !(volume <= 1.f) /* handle nan */) {
    720                     throw new IllegalArgumentException("volume must be >= 0.f and <= 1.f");
    721                 }
    722             }
    723         }
    724 
    725         private static void clampVolume(float[] volumes, boolean log) {
    726             if (log) {
    727                 for (int i = 0; i < volumes.length; ++i) {
    728                     if (!(volumes[i] <= 0.f) /* handle nan */) {
    729                         volumes[i] = 0.f;
    730                     }
    731                 }
    732             } else {
    733                 for (int i = 0; i < volumes.length; ++i) {
    734                     if (!(volumes[i] >= 0.f) /* handle nan */) {
    735                         volumes[i] = 0.f;
    736                     } else if (!(volumes[i] <= 1.f)) {
    737                         volumes[i] = 1.f;
    738                     }
    739                 }
    740             }
    741         }
    742 
    743         /**
    744          * Builder class for a {@link VolumeShaper.Configuration} object.
    745          * <p> Here is an example where {@code Builder} is used to define the
    746          * {@link VolumeShaper.Configuration}.
    747          *
    748          * <pre class="prettyprint">
    749          * VolumeShaper.Configuration LINEAR_RAMP =
    750          *         new VolumeShaper.Configuration.Builder()
    751          *             .setInterpolatorType(VolumeShaper.Configuration.INTERPOLATOR_TYPE_LINEAR)
    752          *             .setCurve(new float[] { 0.f, 1.f }, // times
    753          *                       new float[] { 0.f, 1.f }) // volumes
    754          *             .setDuration(1000)
    755          *             .build();
    756          * </pre>
    757          * <p>
    758          */
    759         public static final class Builder {
    760             private int mType = TYPE_SCALE;
    761             private int mId = -1; // invalid
    762             private int mInterpolatorType = INTERPOLATOR_TYPE_CUBIC;
    763             private int mOptionFlags = OPTION_FLAG_CLOCK_TIME;
    764             private double mDurationMs = 1000.;
    765             private float[] mTimes = null;
    766             private float[] mVolumes = null;
    767 
    768             /**
    769              * Constructs a new {@code Builder} with the defaults.
    770              */
    771             public Builder() {
    772             }
    773 
    774             /**
    775              * Constructs a new {@code Builder} with settings
    776              * copied from a given {@code VolumeShaper.Configuration}.
    777              * @param configuration prototypical configuration
    778              *        which will be reused in the new {@code Builder}.
    779              */
    780             public Builder(@NonNull Configuration configuration) {
    781                 mType = configuration.getType();
    782                 mId = configuration.getId();
    783                 mOptionFlags = configuration.getAllOptionFlags();
    784                 mInterpolatorType = configuration.getInterpolatorType();
    785                 mDurationMs = configuration.getDuration();
    786                 mTimes = configuration.getTimes().clone();
    787                 mVolumes = configuration.getVolumes().clone();
    788             }
    789 
    790             /**
    791              * @hide
    792              * Set the {@code id} for system defined shapers.
    793              * @param id the {@code id} to set. If non-negative, then it is used.
    794              *        If -1, then the system is expected to assign one.
    795              * @return the same {@code Builder} instance.
    796              * @throws IllegalArgumentException if {@code id} < -1.
    797              */
    798             public @NonNull Builder setId(int id) {
    799                 if (id < -1) {
    800                     throw new IllegalArgumentException("invalid id: " + id);
    801                 }
    802                 mId = id;
    803                 return this;
    804             }
    805 
    806             /**
    807              * Sets the interpolator type.
    808              *
    809              * If omitted the default interpolator type is {@link #INTERPOLATOR_TYPE_CUBIC}.
    810              *
    811              * @param interpolatorType method of interpolation used for the volume curve.
    812              *        One of {@link #INTERPOLATOR_TYPE_STEP},
    813              *        {@link #INTERPOLATOR_TYPE_LINEAR},
    814              *        {@link #INTERPOLATOR_TYPE_CUBIC},
    815              *        {@link #INTERPOLATOR_TYPE_CUBIC_MONOTONIC}.
    816              * @return the same {@code Builder} instance.
    817              * @throws IllegalArgumentException if {@code interpolatorType} is not valid.
    818              */
    819             public @NonNull Builder setInterpolatorType(@InterpolatorType int interpolatorType) {
    820                 switch (interpolatorType) {
    821                     case INTERPOLATOR_TYPE_STEP:
    822                     case INTERPOLATOR_TYPE_LINEAR:
    823                     case INTERPOLATOR_TYPE_CUBIC:
    824                     case INTERPOLATOR_TYPE_CUBIC_MONOTONIC:
    825                         mInterpolatorType = interpolatorType;
    826                         break;
    827                     default:
    828                         throw new IllegalArgumentException("invalid interpolatorType: "
    829                                 + interpolatorType);
    830                 }
    831                 return this;
    832             }
    833 
    834             /**
    835              * @hide
    836              * Sets the optional flags
    837              *
    838              * If omitted, flags are 0. If {@link #OPTION_FLAG_VOLUME_IN_DBFS} has
    839              * changed the volume curve needs to be set again as the acceptable
    840              * volume domain has changed.
    841              *
    842              * @param optionFlags new value to replace the old {@code optionFlags}.
    843              * @return the same {@code Builder} instance.
    844              * @throws IllegalArgumentException if flag is not recognized.
    845              */
    846             public @NonNull Builder setOptionFlags(@OptionFlag int optionFlags) {
    847                 if ((optionFlags & ~OPTION_FLAG_PUBLIC_ALL) != 0) {
    848                     throw new IllegalArgumentException("invalid bits in flag: " + optionFlags);
    849                 }
    850                 mOptionFlags = mOptionFlags & ~OPTION_FLAG_PUBLIC_ALL | optionFlags;
    851                 return this;
    852             }
    853 
    854             /**
    855              * Sets the {@code VolumeShaper} duration in milliseconds.
    856              *
    857              * If omitted, the default duration is 1 second.
    858              *
    859              * @param durationMillis
    860              * @return the same {@code Builder} instance.
    861              * @throws IllegalArgumentException if {@code durationMillis}
    862              *         is not strictly positive.
    863              */
    864             public @NonNull Builder setDuration(long durationMillis) {
    865                 if (durationMillis <= 0) {
    866                     throw new IllegalArgumentException(
    867                             "duration: " + durationMillis + " not positive");
    868                 }
    869                 mDurationMs = (double) durationMillis;
    870                 return this;
    871             }
    872 
    873             /**
    874              * Sets the volume curve.
    875              *
    876              * The volume curve is represented by a set of control points given by
    877              * two float arrays of equal length,
    878              * one representing the time (x) coordinates
    879              * and one corresponding to the volume (y) coordinates.
    880              * The length must be at least 2
    881              * and no greater than {@link VolumeShaper.Configuration#getMaximumCurvePoints()}.
    882              * <p>
    883              * The volume curve is normalized as follows:
    884              * time (x) coordinates should be monotonically increasing, from 0.f to 1.f;
    885              * volume (y) coordinates must be within 0.f to 1.f.
    886              * <p>
    887              * The time scale is set by {@link #setDuration}.
    888              * <p>
    889              * @param times an array of float values representing
    890              *        the time line of the volume curve.
    891              * @param volumes an array of float values representing
    892              *        the amplitude of the volume curve.
    893              * @return the same {@code Builder} instance.
    894              * @throws IllegalArgumentException if {@code times} or {@code volumes} is invalid.
    895              */
    896 
    897             /* Note: volume (y) coordinates must be non-positive for log scaling,
    898              * if {@link VolumeShaper.Configuration#OPTION_FLAG_VOLUME_IN_DBFS} is set.
    899              */
    900 
    901             public @NonNull Builder setCurve(@NonNull float[] times, @NonNull float[] volumes) {
    902                 final boolean log = (mOptionFlags & OPTION_FLAG_VOLUME_IN_DBFS) != 0;
    903                 checkCurveForErrorsAndThrowException(times, volumes, log, false /* ise */);
    904                 mTimes = times.clone();
    905                 mVolumes = volumes.clone();
    906                 return this;
    907             }
    908 
    909             /**
    910              * Reflects the volume curve so that
    911              * the shaper changes volume from the end
    912              * to the start.
    913              *
    914              * @return the same {@code Builder} instance.
    915              * @throws IllegalStateException if curve has not been set.
    916              */
    917             public @NonNull Builder reflectTimes() {
    918                 final boolean log = (mOptionFlags & OPTION_FLAG_VOLUME_IN_DBFS) != 0;
    919                 checkCurveForErrorsAndThrowException(mTimes, mVolumes, log, true /* ise */);
    920                 int i;
    921                 for (i = 0; i < mTimes.length / 2; ++i) {
    922                     float temp = mTimes[i];
    923                     mTimes[i] = 1.f - mTimes[mTimes.length - 1 - i];
    924                     mTimes[mTimes.length - 1 - i] = 1.f - temp;
    925                     temp = mVolumes[i];
    926                     mVolumes[i] = mVolumes[mVolumes.length - 1 - i];
    927                     mVolumes[mVolumes.length - 1 - i] = temp;
    928                 }
    929                 if ((mTimes.length & 1) != 0) {
    930                     mTimes[i] = 1.f - mTimes[i];
    931                 }
    932                 return this;
    933             }
    934 
    935             /**
    936              * Inverts the volume curve so that the max volume
    937              * becomes the min volume and vice versa.
    938              *
    939              * @return the same {@code Builder} instance.
    940              * @throws IllegalStateException if curve has not been set.
    941              */
    942             public @NonNull Builder invertVolumes() {
    943                 final boolean log = (mOptionFlags & OPTION_FLAG_VOLUME_IN_DBFS) != 0;
    944                 checkCurveForErrorsAndThrowException(mTimes, mVolumes, log, true /* ise */);
    945                 float min = mVolumes[0];
    946                 float max = mVolumes[0];
    947                 for (int i = 1; i < mVolumes.length; ++i) {
    948                     if (mVolumes[i] < min) {
    949                         min = mVolumes[i];
    950                     } else if (mVolumes[i] > max) {
    951                         max = mVolumes[i];
    952                     }
    953                 }
    954 
    955                 final float maxmin = max + min;
    956                 for (int i = 0; i < mVolumes.length; ++i) {
    957                     mVolumes[i] = maxmin - mVolumes[i];
    958                 }
    959                 return this;
    960             }
    961 
    962             /**
    963              * Scale the curve end volume to a target value.
    964              *
    965              * Keeps the start volume the same.
    966              * This works best if the volume curve is monotonic.
    967              *
    968              * @param volume the target end volume to use.
    969              * @return the same {@code Builder} instance.
    970              * @throws IllegalArgumentException if {@code volume} is not valid.
    971              * @throws IllegalStateException if curve has not been set.
    972              */
    973             public @NonNull Builder scaleToEndVolume(float volume) {
    974                 final boolean log = (mOptionFlags & OPTION_FLAG_VOLUME_IN_DBFS) != 0;
    975                 checkCurveForErrorsAndThrowException(mTimes, mVolumes, log, true /* ise */);
    976                 checkValidVolumeAndThrowException(volume, log);
    977                 final float startVolume = mVolumes[0];
    978                 final float endVolume = mVolumes[mVolumes.length - 1];
    979                 if (endVolume == startVolume) {
    980                     // match with linear ramp
    981                     final float offset = volume - startVolume;
    982                     for (int i = 0; i < mVolumes.length; ++i) {
    983                         mVolumes[i] = mVolumes[i] + offset * mTimes[i];
    984                     }
    985                 } else {
    986                     // scale
    987                     final float scale = (volume - startVolume) / (endVolume - startVolume);
    988                     for (int i = 0; i < mVolumes.length; ++i) {
    989                         mVolumes[i] = scale * (mVolumes[i] - startVolume) + startVolume;
    990                     }
    991                 }
    992                 clampVolume(mVolumes, log);
    993                 return this;
    994             }
    995 
    996             /**
    997              * Scale the curve start volume to a target value.
    998              *
    999              * Keeps the end volume the same.
   1000              * This works best if the volume curve is monotonic.
   1001              *
   1002              * @param volume the target start volume to use.
   1003              * @return the same {@code Builder} instance.
   1004              * @throws IllegalArgumentException if {@code volume} is not valid.
   1005              * @throws IllegalStateException if curve has not been set.
   1006              */
   1007             public @NonNull Builder scaleToStartVolume(float volume) {
   1008                 final boolean log = (mOptionFlags & OPTION_FLAG_VOLUME_IN_DBFS) != 0;
   1009                 checkCurveForErrorsAndThrowException(mTimes, mVolumes, log, true /* ise */);
   1010                 checkValidVolumeAndThrowException(volume, log);
   1011                 final float startVolume = mVolumes[0];
   1012                 final float endVolume = mVolumes[mVolumes.length - 1];
   1013                 if (endVolume == startVolume) {
   1014                     // match with linear ramp
   1015                     final float offset = volume - startVolume;
   1016                     for (int i = 0; i < mVolumes.length; ++i) {
   1017                         mVolumes[i] = mVolumes[i] + offset * (1.f - mTimes[i]);
   1018                     }
   1019                 } else {
   1020                     final float scale = (volume - endVolume) / (startVolume - endVolume);
   1021                     for (int i = 0; i < mVolumes.length; ++i) {
   1022                         mVolumes[i] = scale * (mVolumes[i] - endVolume) + endVolume;
   1023                     }
   1024                 }
   1025                 clampVolume(mVolumes, log);
   1026                 return this;
   1027             }
   1028 
   1029             /**
   1030              * Builds a new {@link VolumeShaper} object.
   1031              *
   1032              * @return a new {@link VolumeShaper} object.
   1033              * @throws IllegalStateException if curve is not properly set.
   1034              */
   1035             public @NonNull Configuration build() {
   1036                 final boolean log = (mOptionFlags & OPTION_FLAG_VOLUME_IN_DBFS) != 0;
   1037                 checkCurveForErrorsAndThrowException(mTimes, mVolumes, log, true /* ise */);
   1038                 return new Configuration(mType, mId, mOptionFlags, mDurationMs,
   1039                         mInterpolatorType, mTimes, mVolumes);
   1040             }
   1041         } // Configuration.Builder
   1042     } // Configuration
   1043 
   1044     /**
   1045      * The {@code VolumeShaper.Operation} class is used to specify operations
   1046      * to the {@code VolumeShaper} that affect the volume change.
   1047      */
   1048     public static final class Operation implements Parcelable {
   1049         /**
   1050          * Forward playback from current volume time position.
   1051          * At the end of the {@code VolumeShaper} curve,
   1052          * the last volume value persists.
   1053          */
   1054         public static final Operation PLAY =
   1055                 new VolumeShaper.Operation.Builder()
   1056                     .build();
   1057 
   1058         /**
   1059          * Reverse playback from current volume time position.
   1060          * When the position reaches the start of the {@code VolumeShaper} curve,
   1061          * the first volume value persists.
   1062          */
   1063         public static final Operation REVERSE =
   1064                 new VolumeShaper.Operation.Builder()
   1065                     .reverse()
   1066                     .build();
   1067 
   1068         // No user serviceable parts below.
   1069 
   1070         // These flags must match the native VolumeShaper::Operation::Flag
   1071         /** @hide */
   1072         @IntDef({
   1073             FLAG_NONE,
   1074             FLAG_REVERSE,
   1075             FLAG_TERMINATE,
   1076             FLAG_JOIN,
   1077             FLAG_DEFER,
   1078             })
   1079         @Retention(RetentionPolicy.SOURCE)
   1080         public @interface Flag {}
   1081 
   1082         /**
   1083          * No special {@code VolumeShaper} operation.
   1084          */
   1085         private static final int FLAG_NONE = 0;
   1086 
   1087         /**
   1088          * Reverse the {@code VolumeShaper} progress.
   1089          *
   1090          * Reverses the {@code VolumeShaper} curve from its current
   1091          * position. If the {@code VolumeShaper} curve has not started,
   1092          * it automatically is considered finished.
   1093          */
   1094         private static final int FLAG_REVERSE = 1 << 0;
   1095 
   1096         /**
   1097          * Terminate the existing {@code VolumeShaper}.
   1098          * This flag is generally used by itself;
   1099          * it takes precedence over all other flags.
   1100          */
   1101         private static final int FLAG_TERMINATE = 1 << 1;
   1102 
   1103         /**
   1104          * Attempt to join as best as possible to the previous {@code VolumeShaper}.
   1105          * This requires the previous {@code VolumeShaper} to be active and
   1106          * {@link #setReplaceId} to be set.
   1107          */
   1108         private static final int FLAG_JOIN = 1 << 2;
   1109 
   1110         /**
   1111          * Defer playback until next operation is sent. This is used
   1112          * when starting a {@code VolumeShaper} effect.
   1113          */
   1114         private static final int FLAG_DEFER = 1 << 3;
   1115 
   1116         /**
   1117          * Use the id specified in the configuration, creating
   1118          * {@code VolumeShaper} as needed; the configuration should be
   1119          * TYPE_SCALE.
   1120          */
   1121         private static final int FLAG_CREATE_IF_NEEDED = 1 << 4;
   1122 
   1123         private static final int FLAG_PUBLIC_ALL = FLAG_REVERSE | FLAG_TERMINATE;
   1124 
   1125         private final int mFlags;
   1126         private final int mReplaceId;
   1127         private final float mXOffset;
   1128 
   1129         @Override
   1130         public String toString() {
   1131             return "VolumeShaper.Operation{"
   1132                     + "mFlags = 0x" + Integer.toHexString(mFlags).toUpperCase()
   1133                     + ", mReplaceId = " + mReplaceId
   1134                     + ", mXOffset = " + mXOffset
   1135                     + "}";
   1136         }
   1137 
   1138         @Override
   1139         public int hashCode() {
   1140             return Objects.hash(mFlags, mReplaceId, mXOffset);
   1141         }
   1142 
   1143         @Override
   1144         public boolean equals(Object o) {
   1145             if (!(o instanceof Operation)) return false;
   1146             if (o == this) return true;
   1147             final Operation other = (Operation) o;
   1148 
   1149             return mFlags == other.mFlags
   1150                     && mReplaceId == other.mReplaceId
   1151                     && Float.compare(mXOffset, other.mXOffset) == 0;
   1152         }
   1153 
   1154         @Override
   1155         public int describeContents() {
   1156             return 0;
   1157         }
   1158 
   1159         @Override
   1160         public void writeToParcel(Parcel dest, int flags) {
   1161             // this needs to match the native VolumeShaper.Operation parceling
   1162             dest.writeInt(mFlags);
   1163             dest.writeInt(mReplaceId);
   1164             dest.writeFloat(mXOffset);
   1165         }
   1166 
   1167         public static final Parcelable.Creator<VolumeShaper.Operation> CREATOR
   1168                 = new Parcelable.Creator<VolumeShaper.Operation>() {
   1169             @Override
   1170             public VolumeShaper.Operation createFromParcel(Parcel p) {
   1171                 // this needs to match the native VolumeShaper.Operation parceling
   1172                 final int flags = p.readInt();
   1173                 final int replaceId = p.readInt();
   1174                 final float xOffset = p.readFloat();
   1175 
   1176                 return new VolumeShaper.Operation(
   1177                         flags
   1178                         , replaceId
   1179                         , xOffset);
   1180             }
   1181 
   1182             @Override
   1183             public VolumeShaper.Operation[] newArray(int size) {
   1184                 return new VolumeShaper.Operation[size];
   1185             }
   1186         };
   1187 
   1188         private Operation(@Flag int flags, int replaceId, float xOffset) {
   1189             mFlags = flags;
   1190             mReplaceId = replaceId;
   1191             mXOffset = xOffset;
   1192         }
   1193 
   1194         /**
   1195          * @hide
   1196          * {@code Builder} class for {@link VolumeShaper.Operation} object.
   1197          *
   1198          * Not for public use.
   1199          */
   1200         public static final class Builder {
   1201             int mFlags;
   1202             int mReplaceId;
   1203             float mXOffset;
   1204 
   1205             /**
   1206              * Constructs a new {@code Builder} with the defaults.
   1207              */
   1208             public Builder() {
   1209                 mFlags = 0;
   1210                 mReplaceId = -1;
   1211                 mXOffset = Float.NaN;
   1212             }
   1213 
   1214             /**
   1215              * Constructs a new {@code Builder} from a given {@code VolumeShaper.Operation}
   1216              * @param operation the {@code VolumeShaper.operation} whose data will be
   1217              *        reused in the new {@code Builder}.
   1218              */
   1219             public Builder(@NonNull VolumeShaper.Operation operation) {
   1220                 mReplaceId = operation.mReplaceId;
   1221                 mFlags = operation.mFlags;
   1222                 mXOffset = operation.mXOffset;
   1223             }
   1224 
   1225             /**
   1226              * Replaces the previous {@code VolumeShaper} specified by {@code id}.
   1227              *
   1228              * The {@code VolumeShaper} specified by the {@code id} is removed
   1229              * if it exists. The configuration should be TYPE_SCALE.
   1230              *
   1231              * @param id the {@code id} of the previous {@code VolumeShaper}.
   1232              * @param join if true, match the volume of the previous
   1233              * shaper to the start volume of the new {@code VolumeShaper}.
   1234              * @return the same {@code Builder} instance.
   1235              */
   1236             public @NonNull Builder replace(int id, boolean join) {
   1237                 mReplaceId = id;
   1238                 if (join) {
   1239                     mFlags |= FLAG_JOIN;
   1240                 } else {
   1241                     mFlags &= ~FLAG_JOIN;
   1242                 }
   1243                 return this;
   1244             }
   1245 
   1246             /**
   1247              * Defers all operations.
   1248              * @return the same {@code Builder} instance.
   1249              */
   1250             public @NonNull Builder defer() {
   1251                 mFlags |= FLAG_DEFER;
   1252                 return this;
   1253             }
   1254 
   1255             /**
   1256              * Terminates the {@code VolumeShaper}.
   1257              *
   1258              * Do not call directly, use {@link VolumeShaper#close()}.
   1259              * @return the same {@code Builder} instance.
   1260              */
   1261             public @NonNull Builder terminate() {
   1262                 mFlags |= FLAG_TERMINATE;
   1263                 return this;
   1264             }
   1265 
   1266             /**
   1267              * Reverses direction.
   1268              * @return the same {@code Builder} instance.
   1269              */
   1270             public @NonNull Builder reverse() {
   1271                 mFlags ^= FLAG_REVERSE;
   1272                 return this;
   1273             }
   1274 
   1275             /**
   1276              * Use the id specified in the configuration, creating
   1277              * {@code VolumeShaper} only as needed; the configuration should be
   1278              * TYPE_SCALE.
   1279              *
   1280              * If the {@code VolumeShaper} with the same id already exists
   1281              * then the operation has no effect.
   1282              *
   1283              * @return the same {@code Builder} instance.
   1284              */
   1285             public @NonNull Builder createIfNeeded() {
   1286                 mFlags |= FLAG_CREATE_IF_NEEDED;
   1287                 return this;
   1288             }
   1289 
   1290             /**
   1291              * Sets the {@code xOffset} to use for the {@code VolumeShaper}.
   1292              *
   1293              * The {@code xOffset} is the position on the volume curve,
   1294              * and setting takes effect when the {@code VolumeShaper} is used next.
   1295              *
   1296              * @param xOffset a value between (or equal to) 0.f and 1.f, or Float.NaN to ignore.
   1297              * @return the same {@code Builder} instance.
   1298              * @throws IllegalArgumentException if {@code xOffset} is not between 0.f and 1.f,
   1299              *         or a Float.NaN.
   1300              */
   1301             public @NonNull Builder setXOffset(float xOffset) {
   1302                 if (xOffset < -0.f) {
   1303                     throw new IllegalArgumentException("Negative xOffset not allowed");
   1304                 } else if (xOffset > 1.f) {
   1305                     throw new IllegalArgumentException("xOffset > 1.f not allowed");
   1306                 }
   1307                 // Float.NaN passes through
   1308                 mXOffset = xOffset;
   1309                 return this;
   1310             }
   1311 
   1312             /**
   1313              * Sets the operation flag.  Do not call this directly but one of the
   1314              * other builder methods.
   1315              *
   1316              * @param flags new value for {@code flags}, consisting of ORed flags.
   1317              * @return the same {@code Builder} instance.
   1318              * @throws IllegalArgumentException if {@code flags} contains invalid set bits.
   1319              */
   1320             private @NonNull Builder setFlags(@Flag int flags) {
   1321                 if ((flags & ~FLAG_PUBLIC_ALL) != 0) {
   1322                     throw new IllegalArgumentException("flag has unknown bits set: " + flags);
   1323                 }
   1324                 mFlags = mFlags & ~FLAG_PUBLIC_ALL | flags;
   1325                 return this;
   1326             }
   1327 
   1328             /**
   1329              * Builds a new {@link VolumeShaper.Operation} object.
   1330              *
   1331              * @return a new {@code VolumeShaper.Operation} object
   1332              */
   1333             public @NonNull Operation build() {
   1334                 return new Operation(mFlags, mReplaceId, mXOffset);
   1335             }
   1336         } // Operation.Builder
   1337     } // Operation
   1338 
   1339     /**
   1340      * @hide
   1341      * {@code VolumeShaper.State} represents the current progress
   1342      * of the {@code VolumeShaper}.
   1343      *
   1344      *  Not for public use.
   1345      */
   1346     public static final class State implements Parcelable {
   1347         private float mVolume;
   1348         private float mXOffset;
   1349 
   1350         @Override
   1351         public String toString() {
   1352             return "VolumeShaper.State{"
   1353                     + "mVolume = " + mVolume
   1354                     + ", mXOffset = " + mXOffset
   1355                     + "}";
   1356         }
   1357 
   1358         @Override
   1359         public int hashCode() {
   1360             return Objects.hash(mVolume, mXOffset);
   1361         }
   1362 
   1363         @Override
   1364         public boolean equals(Object o) {
   1365             if (!(o instanceof State)) return false;
   1366             if (o == this) return true;
   1367             final State other = (State) o;
   1368             return mVolume == other.mVolume
   1369                     && mXOffset == other.mXOffset;
   1370         }
   1371 
   1372         @Override
   1373         public int describeContents() {
   1374             return 0;
   1375         }
   1376 
   1377         @Override
   1378         public void writeToParcel(Parcel dest, int flags) {
   1379             dest.writeFloat(mVolume);
   1380             dest.writeFloat(mXOffset);
   1381         }
   1382 
   1383         public static final Parcelable.Creator<VolumeShaper.State> CREATOR
   1384                 = new Parcelable.Creator<VolumeShaper.State>() {
   1385             @Override
   1386             public VolumeShaper.State createFromParcel(Parcel p) {
   1387                 return new VolumeShaper.State(
   1388                         p.readFloat()     // volume
   1389                         , p.readFloat()); // xOffset
   1390             }
   1391 
   1392             @Override
   1393             public VolumeShaper.State[] newArray(int size) {
   1394                 return new VolumeShaper.State[size];
   1395             }
   1396         };
   1397 
   1398         /* package */ State(float volume, float xOffset) {
   1399             mVolume = volume;
   1400             mXOffset = xOffset;
   1401         }
   1402 
   1403         /**
   1404          * Gets the volume of the {@link VolumeShaper.State}.
   1405          * @return linear volume between 0.f and 1.f.
   1406          */
   1407         public float getVolume() {
   1408             return mVolume;
   1409         }
   1410 
   1411         /**
   1412          * Gets the {@code xOffset} position on the normalized curve
   1413          * of the {@link VolumeShaper.State}.
   1414          * @return the curve x position between 0.f and 1.f.
   1415          */
   1416         public float getXOffset() {
   1417             return mXOffset;
   1418         }
   1419     } // State
   1420 }
   1421