/*
 * Copyright 2017 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package android.media;

import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.TestApi;
import android.os.Parcel;
import android.os.Parcelable;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.AutoCloseable;
import java.lang.ref.WeakReference;
import java.util.Arrays;
import java.util.Objects;

The VolumeShaper class is used to automatically control audio volume during media playback, allowing simple implementation of transition effects and ducking. It is created from implementations of VolumeAutomation, such as MediaPlayer and AudioTrack (referred to as "players" below), by MediaPlayer.createVolumeShaper or AudioTrack.createVolumeShaper. A VolumeShaper is intended for short volume changes. If the audio output sink changes during a VolumeShaper transition, the precise curve position may be lost, and the VolumeShaper may advance to the end of the curve for the new audio output sink. The VolumeShaper appears as an additional scaling on the audio output, and adjusts independently of track or stream volume controls.
/** * The {@code VolumeShaper} class is used to automatically control audio volume during media * playback, allowing simple implementation of transition effects and ducking. * It is created from implementations of {@code VolumeAutomation}, * such as {@code MediaPlayer} and {@code AudioTrack} (referred to as "players" below), * by {@link MediaPlayer#createVolumeShaper} or {@link AudioTrack#createVolumeShaper}. * * A {@code VolumeShaper} is intended for short volume changes. * If the audio output sink changes during * a {@code VolumeShaper} transition, the precise curve position may be lost, and the * {@code VolumeShaper} may advance to the end of the curve for the new audio output sink. * * The {@code VolumeShaper} appears as an additional scaling on the audio output, * and adjusts independently of track or stream volume controls. */
public final class VolumeShaper implements AutoCloseable { /* member variables */ private int mId; private final WeakReference<PlayerBase> mWeakPlayerBase; /* package */ VolumeShaper( @NonNull Configuration configuration, @NonNull PlayerBase playerBase) { mWeakPlayerBase = new WeakReference<PlayerBase>(playerBase); mId = applyPlayer(configuration, new Operation.Builder().defer().build()); } /* package */ int getId() { return mId; }
Applies the Operation to the VolumeShaper. Applying Operation.PLAY after PLAY or Operation.REVERSE after REVERSE has no effect. Applying Operation.PLAY when the player hasn't started will synchronously start the VolumeShaper when playback begins.
Params:
  • operation – the operation to apply.
Throws:
  • IllegalStateException – if the player is uninitialized or if there is a critical failure. In that case, the VolumeShaper should be recreated.
/** * Applies the {@link VolumeShaper.Operation} to the {@code VolumeShaper}. * * Applying {@link VolumeShaper.Operation#PLAY} after {@code PLAY} * or {@link VolumeShaper.Operation#REVERSE} after * {@code REVERSE} has no effect. * * Applying {@link VolumeShaper.Operation#PLAY} when the player * hasn't started will synchronously start the {@code VolumeShaper} when * playback begins. * * @param operation the {@code operation} to apply. * @throws IllegalStateException if the player is uninitialized or if there * is a critical failure. In that case, the {@code VolumeShaper} should be * recreated. */
public void apply(@NonNull Operation operation) { /* void */ applyPlayer(new VolumeShaper.Configuration(mId), operation); }
Replaces the current VolumeShaper configuration with a new configuration. This allows the user to change the volume shape while the existing VolumeShaper is in effect. The effect of replace() is similar to an atomic close of the existing VolumeShaper and creation of a new VolumeShaper. If the operation is Operation.PLAY then the new curve starts immediately. If the operation is Operation.REVERSE, then the new curve will be delayed until PLAY is applied.
Params:
  • configuration – the new configuration to use.
  • operation – the operation to apply to the VolumeShaper
  • join – if true, match the start volume of the new configuration to the current volume of the existing VolumeShaper, to avoid discontinuity.
Throws:
  • IllegalStateException – if the player is uninitialized or if there is a critical failure. In that case, the VolumeShaper should be recreated.
/** * Replaces the current {@code VolumeShaper} * {@code configuration} with a new {@code configuration}. * * This allows the user to change the volume shape * while the existing {@code VolumeShaper} is in effect. * * The effect of {@code replace()} is similar to an atomic close of * the existing {@code VolumeShaper} and creation of a new {@code VolumeShaper}. * * If the {@code operation} is {@link VolumeShaper.Operation#PLAY} then the * new curve starts immediately. * * If the {@code operation} is * {@link VolumeShaper.Operation#REVERSE}, then the new curve will * be delayed until {@code PLAY} is applied. * * @param configuration the new {@code configuration} to use. * @param operation the {@code operation} to apply to the {@code VolumeShaper} * @param join if true, match the start volume of the * new {@code configuration} to the current volume of the existing * {@code VolumeShaper}, to avoid discontinuity. * @throws IllegalStateException if the player is uninitialized or if there * is a critical failure. In that case, the {@code VolumeShaper} should be * recreated. */
public void replace( @NonNull Configuration configuration, @NonNull Operation operation, boolean join) { mId = applyPlayer( configuration, new Operation.Builder(operation).replace(mId, join).build()); }
Returns the current volume scale attributable to the VolumeShaper. This is the last volume from the VolumeShaper used for the player, or the initial volume if the VolumeShaper hasn't been started with Operation.PLAY.
Throws:
  • IllegalStateException – if the player is uninitialized or if there is a critical failure. In that case, the VolumeShaper should be recreated.
Returns:the volume, linearly represented as a value between 0.f and 1.f.
/** * Returns the current volume scale attributable to the {@code VolumeShaper}. * * This is the last volume from the {@code VolumeShaper} used for the player, * or the initial volume if the {@code VolumeShaper} hasn't been started with * {@link VolumeShaper.Operation#PLAY}. * * @return the volume, linearly represented as a value between 0.f and 1.f. * @throws IllegalStateException if the player is uninitialized or if there * is a critical failure. In that case, the {@code VolumeShaper} should be * recreated. */
public float getVolume() { return getStatePlayer(mId).getVolume(); }
Releases the VolumeShaper object; any volume scale due to the VolumeShaper is removed after closing. If the volume does not reach 1.f when the VolumeShaper is closed (or finalized), there may be an abrupt change of volume. close() may be safely called after a prior close(). This class implements the Java AutoClosable interface and may be used with try-with-resources.
/** * Releases the {@code VolumeShaper} object; any volume scale due to the * {@code VolumeShaper} is removed after closing. * * If the volume does not reach 1.f when the {@code VolumeShaper} is closed * (or finalized), there may be an abrupt change of volume. * * {@code close()} may be safely called after a prior {@code close()}. * This class implements the Java {@code AutoClosable} interface and * may be used with try-with-resources. */
@Override public void close() { try { /* void */ applyPlayer( new VolumeShaper.Configuration(mId), new Operation.Builder().terminate().build()); } catch (IllegalStateException ise) { ; // ok } if (mWeakPlayerBase != null) { mWeakPlayerBase.clear(); } } @Override protected void finalize() { close(); // ensure we remove the native VolumeShaper }
Internal call to apply the configuration and operation to the player. Returns a valid shaper id or throws the appropriate exception.
Params:
  • configuration –
  • operation –
Throws:
Returns:id a non-negative shaper id.
/** * Internal call to apply the {@code configuration} and {@code operation} to the player. * Returns a valid shaper id or throws the appropriate exception. * @param configuration * @param operation * @return id a non-negative shaper id. * @throws IllegalStateException if the player has been deallocated or is uninitialized. */
private int applyPlayer( @NonNull VolumeShaper.Configuration configuration, @NonNull VolumeShaper.Operation operation) { final int id; if (mWeakPlayerBase != null) { PlayerBase player = mWeakPlayerBase.get(); if (player == null) { throw new IllegalStateException("player deallocated"); } id = player.playerApplyVolumeShaper(configuration, operation); } else { throw new IllegalStateException("uninitialized shaper"); } if (id < 0) { // TODO - get INVALID_OPERATION from platform. final int VOLUME_SHAPER_INVALID_OPERATION = -38; // must match with platform // Due to RPC handling, we translate integer codes to exceptions right before // delivering to the user. if (id == VOLUME_SHAPER_INVALID_OPERATION) { throw new IllegalStateException("player or VolumeShaper deallocated"); } else { throw new IllegalArgumentException("invalid configuration or operation: " + id); } } return id; }
Internal call to retrieve the current VolumeShaper state.
Params:
  • id –
Throws:
Returns:the current VolumeShaper.State
/** * Internal call to retrieve the current {@code VolumeShaper} state. * @param id * @return the current {@code VolumeShaper.State} * @throws IllegalStateException if the player has been deallocated or is uninitialized. */
private @NonNull VolumeShaper.State getStatePlayer(int id) { final VolumeShaper.State state; if (mWeakPlayerBase != null) { PlayerBase player = mWeakPlayerBase.get(); if (player == null) { throw new IllegalStateException("player deallocated"); } state = player.playerGetVolumeShaperState(id); } else { throw new IllegalStateException("uninitialized shaper"); } if (state == null) { throw new IllegalStateException("shaper cannot be found"); } return state; }
The VolumeShaper.Configuration class contains curve and duration information. It is constructed by the Builder.

A VolumeShaper.Configuration is used by VolumeAutomation.createVolumeShaper(Configuration) to create a VolumeShaper and by VolumeShaper.replace(Configuration, Operation, boolean) to replace an existing configuration.

The AudioTrack and MediaPlayer classes implement the VolumeAutomation interface.

/** * The {@code VolumeShaper.Configuration} class contains curve * and duration information. * It is constructed by the {@link VolumeShaper.Configuration.Builder}. * <p> * A {@code VolumeShaper.Configuration} is used by * {@link VolumeAutomation#createVolumeShaper(Configuration) * VolumeAutomation.createVolumeShaper(Configuration)} to create * a {@code VolumeShaper} and * by {@link VolumeShaper#replace(Configuration, Operation, boolean) * VolumeShaper.replace(Configuration, Operation, boolean)} * to replace an existing {@code configuration}. * <p> * The {@link AudioTrack} and {@link MediaPlayer} classes implement * the {@link VolumeAutomation} interface. */
public static final class Configuration implements Parcelable { private static final int MAXIMUM_CURVE_POINTS = 16;
Returns the maximum number of curve points allowed for setCurve.setCurve(float[], float[]).
/** * Returns the maximum number of curve points allowed for * {@link VolumeShaper.Builder#setCurve(float[], float[])}. */
public static int getMaximumCurvePoints() { return MAXIMUM_CURVE_POINTS; } // These values must match the native VolumeShaper::Configuration::Type
@hide
/** @hide */
@IntDef({ TYPE_ID, TYPE_SCALE, }) @Retention(RetentionPolicy.SOURCE) public @interface Type {}
Specifies a VolumeShaper handle created by VolumeShaper(int) from an id returned by setVolumeShaper(). The type, curve, etc. may not be queried from a VolumeShaper object of this type; the handle is used to identify and change the operation of an existing VolumeShaper sent to the player.
/** * Specifies a {@link VolumeShaper} handle created by {@link #VolumeShaper(int)} * from an id returned by {@code setVolumeShaper()}. * The type, curve, etc. may not be queried from * a {@code VolumeShaper} object of this type; * the handle is used to identify and change the operation of * an existing {@code VolumeShaper} sent to the player. */
/* package */ static final int TYPE_ID = 0;
Specifies a VolumeShaper to be used as an additional scale to the current volume. This is created by the Builder.
/** * Specifies a {@link VolumeShaper} to be used * as an additional scale to the current volume. * This is created by the {@link VolumeShaper.Builder}. */
/* package */ static final int TYPE_SCALE = 1; // These values must match the native InterpolatorType enumeration.
@hide
/** @hide */
@IntDef({ INTERPOLATOR_TYPE_STEP, INTERPOLATOR_TYPE_LINEAR, INTERPOLATOR_TYPE_CUBIC, INTERPOLATOR_TYPE_CUBIC_MONOTONIC, }) @Retention(RetentionPolicy.SOURCE) public @interface InterpolatorType {}
Stepwise volume curve.
/** * Stepwise volume curve. */
public static final int INTERPOLATOR_TYPE_STEP = 0;
Linear interpolated volume curve.
/** * Linear interpolated volume curve. */
public static final int INTERPOLATOR_TYPE_LINEAR = 1;
Cubic interpolated volume curve. This is default if unspecified.
/** * Cubic interpolated volume curve. * This is default if unspecified. */
public static final int INTERPOLATOR_TYPE_CUBIC = 2;
Cubic interpolated volume curve that preserves local monotonicity. So long as the control points are locally monotonic, the curve interpolation between those points are monotonic. This is useful for cubic spline interpolated volume ramps and ducks.
/** * Cubic interpolated volume curve * that preserves local monotonicity. * So long as the control points are locally monotonic, * the curve interpolation between those points are monotonic. * This is useful for cubic spline interpolated * volume ramps and ducks. */
public static final int INTERPOLATOR_TYPE_CUBIC_MONOTONIC = 3; // These values must match the native VolumeShaper::Configuration::InterpolatorType
@hide
/** @hide */
@IntDef({ OPTION_FLAG_VOLUME_IN_DBFS, OPTION_FLAG_CLOCK_TIME, }) @Retention(RetentionPolicy.SOURCE) public @interface OptionFlag {}
@hide Use a dB full scale volume range for the volume curve.

The volume scale is typically from 0.f to 1.f on a linear scale; this option changes to -inf to 0.f on a db full scale, where 0.f is equivalent to a scale of 1.f.

/** * @hide * Use a dB full scale volume range for the volume curve. *<p> * The volume scale is typically from 0.f to 1.f on a linear scale; * this option changes to -inf to 0.f on a db full scale, * where 0.f is equivalent to a scale of 1.f. */
public static final int OPTION_FLAG_VOLUME_IN_DBFS = (1 << 0);
@hide Use clock time instead of media time.

The default implementation of VolumeShaper is to apply volume changes by the media time of the player. Hence, the VolumeShaper will speed or slow down to match player changes of playback rate, pause, or resume.

The OPTION_FLAG_CLOCK_TIME option allows the VolumeShaper progress to be determined by clock time instead of media time.

/** * @hide * Use clock time instead of media time. *<p> * The default implementation of {@code VolumeShaper} is to apply * volume changes by the media time of the player. * Hence, the {@code VolumeShaper} will speed or slow down to * match player changes of playback rate, pause, or resume. *<p> * The {@code OPTION_FLAG_CLOCK_TIME} option allows the {@code VolumeShaper} * progress to be determined by clock time instead of media time. */
public static final int OPTION_FLAG_CLOCK_TIME = (1 << 1); private static final int OPTION_FLAG_PUBLIC_ALL = OPTION_FLAG_VOLUME_IN_DBFS | OPTION_FLAG_CLOCK_TIME;
A one second linear ramp from silence to full volume. Use reflectTimes.reflectTimes() or invertVolumes.invertVolumes() to generate the matching linear duck.
/** * A one second linear ramp from silence to full volume. * Use {@link VolumeShaper.Builder#reflectTimes()} * or {@link VolumeShaper.Builder#invertVolumes()} to generate * the matching linear duck. */
public static final Configuration LINEAR_RAMP = new VolumeShaper.Configuration.Builder() .setInterpolatorType(INTERPOLATOR_TYPE_LINEAR) .setCurve(new float[] {0.f, 1.f} /* times */, new float[] {0.f, 1.f} /* volumes */) .setDuration(1000) .build();
A one second cubic ramp from silence to full volume. Use reflectTimes.reflectTimes() or invertVolumes.invertVolumes() to generate the matching cubic duck.
/** * A one second cubic ramp from silence to full volume. * Use {@link VolumeShaper.Builder#reflectTimes()} * or {@link VolumeShaper.Builder#invertVolumes()} to generate * the matching cubic duck. */
public static final Configuration CUBIC_RAMP = new VolumeShaper.Configuration.Builder() .setInterpolatorType(INTERPOLATOR_TYPE_CUBIC) .setCurve(new float[] {0.f, 1.f} /* times */, new float[] {0.f, 1.f} /* volumes */) .setDuration(1000) .build();
A one second sine curve from silence to full volume for energy preserving cross fades. Use reflectTimes.reflectTimes() to generate the matching cosine duck.
/** * A one second sine curve * from silence to full volume for energy preserving cross fades. * Use {@link VolumeShaper.Builder#reflectTimes()} to generate * the matching cosine duck. */
public static final Configuration SINE_RAMP;
A one second sine-squared s-curve ramp from silence to full volume. Use reflectTimes.reflectTimes() or invertVolumes.invertVolumes() to generate the matching sine-squared s-curve duck.
/** * A one second sine-squared s-curve ramp * from silence to full volume. * Use {@link VolumeShaper.Builder#reflectTimes()} * or {@link VolumeShaper.Builder#invertVolumes()} to generate * the matching sine-squared s-curve duck. */
public static final Configuration SCURVE_RAMP; static { final int POINTS = MAXIMUM_CURVE_POINTS; final float times[] = new float[POINTS]; final float sines[] = new float[POINTS]; final float scurve[] = new float[POINTS]; for (int i = 0; i < POINTS; ++i) { times[i] = (float)i / (POINTS - 1); final float sine = (float)Math.sin(times[i] * Math.PI / 2.); sines[i] = sine; scurve[i] = sine * sine; } SINE_RAMP = new VolumeShaper.Configuration.Builder() .setInterpolatorType(INTERPOLATOR_TYPE_CUBIC) .setCurve(times, sines) .setDuration(1000) .build(); SCURVE_RAMP = new VolumeShaper.Configuration.Builder() .setInterpolatorType(INTERPOLATOR_TYPE_CUBIC) .setCurve(times, scurve) .setDuration(1000) .build(); } /* * member variables - these are all final */ // type of VolumeShaper private final int mType; // valid when mType is TYPE_ID private final int mId; // valid when mType is TYPE_SCALE private final int mOptionFlags; private final double mDurationMs; private final int mInterpolatorType; private final float[] mTimes; private final float[] mVolumes; @Override public String toString() { return "VolumeShaper.Configuration{" + "mType = " + mType + ", mId = " + mId + (mType == TYPE_ID ? "}" : ", mOptionFlags = 0x" + Integer.toHexString(mOptionFlags).toUpperCase() + ", mDurationMs = " + mDurationMs + ", mInterpolatorType = " + mInterpolatorType + ", mTimes[] = " + Arrays.toString(mTimes) + ", mVolumes[] = " + Arrays.toString(mVolumes) + "}"); } @Override public int hashCode() { return mType == TYPE_ID ? Objects.hash(mType, mId) : Objects.hash(mType, mId, mOptionFlags, mDurationMs, mInterpolatorType, Arrays.hashCode(mTimes), Arrays.hashCode(mVolumes)); } @Override public boolean equals(Object o) { if (!(o instanceof Configuration)) return false; if (o == this) return true; final Configuration other = (Configuration) o; // Note that exact floating point equality may not be guaranteed // for a theoretically idempotent operation; for example, // there are many cases where a + b - b != a. return mType == other.mType && mId == other.mId && (mType == TYPE_ID || (mOptionFlags == other.mOptionFlags && mDurationMs == other.mDurationMs && mInterpolatorType == other.mInterpolatorType && Arrays.equals(mTimes, other.mTimes) && Arrays.equals(mVolumes, other.mVolumes))); } @Override public int describeContents() { return 0; } @Override public void writeToParcel(Parcel dest, int flags) { // this needs to match the native VolumeShaper.Configuration parceling dest.writeInt(mType); dest.writeInt(mId); if (mType != TYPE_ID) { dest.writeInt(mOptionFlags); dest.writeDouble(mDurationMs); // this needs to match the native Interpolator parceling dest.writeInt(mInterpolatorType); dest.writeFloat(0.f); // first slope (specifying for native side) dest.writeFloat(0.f); // last slope (specifying for native side) // mTimes and mVolumes should have the same length. dest.writeInt(mTimes.length); for (int i = 0; i < mTimes.length; ++i) { dest.writeFloat(mTimes[i]); dest.writeFloat(mVolumes[i]); } } } public static final Parcelable.Creator<VolumeShaper.Configuration> CREATOR = new Parcelable.Creator<VolumeShaper.Configuration>() { @Override public VolumeShaper.Configuration createFromParcel(Parcel p) { // this needs to match the native VolumeShaper.Configuration parceling final int type = p.readInt(); final int id = p.readInt(); if (type == TYPE_ID) { return new VolumeShaper.Configuration(id); } else { final int optionFlags = p.readInt(); final double durationMs = p.readDouble(); // this needs to match the native Interpolator parceling final int interpolatorType = p.readInt(); final float firstSlope = p.readFloat(); // ignored on the Java side final float lastSlope = p.readFloat(); // ignored on the Java side final int length = p.readInt(); final float[] times = new float[length]; final float[] volumes = new float[length]; for (int i = 0; i < length; ++i) { times[i] = p.readFloat(); volumes[i] = p.readFloat(); } return new VolumeShaper.Configuration( type, id, optionFlags, durationMs, interpolatorType, times, volumes); } } @Override public VolumeShaper.Configuration[] newArray(int size) { return new VolumeShaper.Configuration[size]; } };
Params:
  • id –
Throws:
@hide Constructs a VolumeShaper from an id. This is an opaque handle for controlling a VolumeShaper that has already been sent to a player. The id is returned from the initial setVolumeShaper() call on success. These configurations are for native use only, they are never returned directly to the user.
/** * @hide * Constructs a {@code VolumeShaper} from an id. * * This is an opaque handle for controlling a {@code VolumeShaper} that has * already been sent to a player. The {@code id} is returned from the * initial {@code setVolumeShaper()} call on success. * * These configurations are for native use only, * they are never returned directly to the user. * * @param id * @throws IllegalArgumentException if id is negative. */
public Configuration(int id) { if (id < 0) { throw new IllegalArgumentException("negative id " + id); } mType = TYPE_ID; mId = id; mInterpolatorType = 0; mOptionFlags = 0; mDurationMs = 0; mTimes = null; mVolumes = null; }
Direct constructor for VolumeShaper. Use the Builder instead.
/** * Direct constructor for VolumeShaper. * Use the Builder instead. */
private Configuration(@Type int type, int id, @OptionFlag int optionFlags, double durationMs, @InterpolatorType int interpolatorType, @NonNull float[] times, @NonNull float[] volumes) { mType = type; mId = id; mOptionFlags = optionFlags; mDurationMs = durationMs; mInterpolatorType = interpolatorType; // Builder should have cloned these arrays already. mTimes = times; mVolumes = volumes; }
@hide Returns the VolumeShaper type.
/** * @hide * Returns the {@code VolumeShaper} type. */
public @Type int getType() { return mType; }
@hide Returns the VolumeShaper id.
/** * @hide * Returns the {@code VolumeShaper} id. */
public int getId() { return mId; }
Returns the interpolator type.
/** * Returns the interpolator type. */
public @InterpolatorType int getInterpolatorType() { return mInterpolatorType; }
@hide Returns the option flags
/** * @hide * Returns the option flags */
public @OptionFlag int getOptionFlags() { return mOptionFlags & OPTION_FLAG_PUBLIC_ALL; } /* package */ @OptionFlag int getAllOptionFlags() { return mOptionFlags; }
Returns the duration of the volume shape in milliseconds.
/** * Returns the duration of the volume shape in milliseconds. */
public long getDuration() { // casting is safe here as the duration was set as a long in the Builder return (long) mDurationMs; }
Returns the times (x) coordinate array of the volume curve points.
/** * Returns the times (x) coordinate array of the volume curve points. */
public float[] getTimes() { return mTimes; }
Returns the volumes (y) coordinate array of the volume curve points.
/** * Returns the volumes (y) coordinate array of the volume curve points. */
public float[] getVolumes() { return mVolumes; }
Checks the validity of times and volumes point representation. times[] and volumes[] are two arrays representing points for the volume curve. Note that times[] and volumes[] are explicitly checked against null here to provide the proper error string - those are legitimate arguments to this method.
Params:
  • times – the x coordinates for the points, must be between 0.f and 1.f and be monotonic.
  • volumes – the y coordinates for the points, must be between 0.f and 1.f for linear and must be no greater than 0.f for log (dBFS).
  • log – set to true if the scale is logarithmic.
Returns:null if no error, or the reason in a String for an error.
/** * Checks the validity of times and volumes point representation. * * {@code times[]} and {@code volumes[]} are two arrays representing points * for the volume curve. * * Note that {@code times[]} and {@code volumes[]} are explicitly checked against * null here to provide the proper error string - those are legitimate * arguments to this method. * * @param times the x coordinates for the points, * must be between 0.f and 1.f and be monotonic. * @param volumes the y coordinates for the points, * must be between 0.f and 1.f for linear and * must be no greater than 0.f for log (dBFS). * @param log set to true if the scale is logarithmic. * @return null if no error, or the reason in a {@code String} for an error. */
private static @Nullable String checkCurveForErrors( @Nullable float[] times, @Nullable float[] volumes, boolean log) { if (times == null) { return "times array must be non-null"; } else if (volumes == null) { return "volumes array must be non-null"; } else if (times.length != volumes.length) { return "array length must match"; } else if (times.length < 2) { return "array length must be at least 2"; } else if (times.length > MAXIMUM_CURVE_POINTS) { return "array length must be no larger than " + MAXIMUM_CURVE_POINTS; } else if (times[0] != 0.f) { return "times must start at 0.f"; } else if (times[times.length - 1] != 1.f) { return "times must end at 1.f"; } // validate points along the curve for (int i = 1; i < times.length; ++i) { if (!(times[i] > times[i - 1]) /* handle nan */) { return "times not monotonic increasing, check index " + i; } } if (log) { for (int i = 0; i < volumes.length; ++i) { if (!(volumes[i] <= 0.f) /* handle nan */) { return "volumes for log scale cannot be positive, " + "check index " + i; } } } else { for (int i = 0; i < volumes.length; ++i) { if (!(volumes[i] >= 0.f) || !(volumes[i] <= 1.f) /* handle nan */) { return "volumes for linear scale must be between 0.f and 1.f, " + "check index " + i; } } } return null; // no errors } private static void checkCurveForErrorsAndThrowException( @Nullable float[] times, @Nullable float[] volumes, boolean log, boolean ise) { final String error = checkCurveForErrors(times, volumes, log); if (error != null) { if (ise) { throw new IllegalStateException(error); } else { throw new IllegalArgumentException(error); } } } private static void checkValidVolumeAndThrowException(float volume, boolean log) { if (log) { if (!(volume <= 0.f) /* handle nan */) { throw new IllegalArgumentException("dbfs volume must be 0.f or less"); } } else { if (!(volume >= 0.f) || !(volume <= 1.f) /* handle nan */) { throw new IllegalArgumentException("volume must be >= 0.f and <= 1.f"); } } } private static void clampVolume(float[] volumes, boolean log) { if (log) { for (int i = 0; i < volumes.length; ++i) { if (!(volumes[i] <= 0.f) /* handle nan */) { volumes[i] = 0.f; } } } else { for (int i = 0; i < volumes.length; ++i) { if (!(volumes[i] >= 0.f) /* handle nan */) { volumes[i] = 0.f; } else if (!(volumes[i] <= 1.f)) { volumes[i] = 1.f; } } } }
Builder class for a Configuration object.

Here is an example where Builder is used to define the Configuration.

VolumeShaper.Configuration LINEAR_RAMP =
        new VolumeShaper.Configuration.Builder()
            .setInterpolatorType(VolumeShaper.Configuration.INTERPOLATOR_TYPE_LINEAR)
            .setCurve(new float[] { 0.f, 1.f }, // times
                      new float[] { 0.f, 1.f }) // volumes
            .setDuration(1000)
            .build();

/** * Builder class for a {@link VolumeShaper.Configuration} object. * <p> Here is an example where {@code Builder} is used to define the * {@link VolumeShaper.Configuration}. * * <pre class="prettyprint"> * VolumeShaper.Configuration LINEAR_RAMP = * new VolumeShaper.Configuration.Builder() * .setInterpolatorType(VolumeShaper.Configuration.INTERPOLATOR_TYPE_LINEAR) * .setCurve(new float[] { 0.f, 1.f }, // times * new float[] { 0.f, 1.f }) // volumes * .setDuration(1000) * .build(); * </pre> * <p> */
public static final class Builder { private int mType = TYPE_SCALE; private int mId = -1; // invalid private int mInterpolatorType = INTERPOLATOR_TYPE_CUBIC; private int mOptionFlags = OPTION_FLAG_CLOCK_TIME; private double mDurationMs = 1000.; private float[] mTimes = null; private float[] mVolumes = null;
Constructs a new Builder with the defaults.
/** * Constructs a new {@code Builder} with the defaults. */
public Builder() { }
Constructs a new Builder with settings copied from a given VolumeShaper.Configuration.
Params:
  • configuration – prototypical configuration which will be reused in the new Builder.
/** * Constructs a new {@code Builder} with settings * copied from a given {@code VolumeShaper.Configuration}. * @param configuration prototypical configuration * which will be reused in the new {@code Builder}. */
public Builder(@NonNull Configuration configuration) { mType = configuration.getType(); mId = configuration.getId(); mOptionFlags = configuration.getAllOptionFlags(); mInterpolatorType = configuration.getInterpolatorType(); mDurationMs = configuration.getDuration(); mTimes = configuration.getTimes().clone(); mVolumes = configuration.getVolumes().clone(); }
Params:
  • id – the id to set. If non-negative, then it is used. If -1, then the system is expected to assign one.
Throws:
@hide Set the id for system defined shapers.
Returns:the same Builder instance.
/** * @hide * Set the {@code id} for system defined shapers. * @param id the {@code id} to set. If non-negative, then it is used. * If -1, then the system is expected to assign one. * @return the same {@code Builder} instance. * @throws IllegalArgumentException if {@code id} < -1. */
public @NonNull Builder setId(int id) { if (id < -1) { throw new IllegalArgumentException("invalid id: " + id); } mId = id; return this; }
Sets the interpolator type. If omitted the default interpolator type is Configuration.INTERPOLATOR_TYPE_CUBIC.
Params:
Throws:
Returns:the same Builder instance.
/** * Sets the interpolator type. * * If omitted the default interpolator type is {@link #INTERPOLATOR_TYPE_CUBIC}. * * @param interpolatorType method of interpolation used for the volume curve. * One of {@link #INTERPOLATOR_TYPE_STEP}, * {@link #INTERPOLATOR_TYPE_LINEAR}, * {@link #INTERPOLATOR_TYPE_CUBIC}, * {@link #INTERPOLATOR_TYPE_CUBIC_MONOTONIC}. * @return the same {@code Builder} instance. * @throws IllegalArgumentException if {@code interpolatorType} is not valid. */
public @NonNull Builder setInterpolatorType(@InterpolatorType int interpolatorType) { switch (interpolatorType) { case INTERPOLATOR_TYPE_STEP: case INTERPOLATOR_TYPE_LINEAR: case INTERPOLATOR_TYPE_CUBIC: case INTERPOLATOR_TYPE_CUBIC_MONOTONIC: mInterpolatorType = interpolatorType; break; default: throw new IllegalArgumentException("invalid interpolatorType: " + interpolatorType); } return this; }
Params:
  • optionFlags – new value to replace the old optionFlags.
Throws:
@hide Sets the optional flags If omitted, flags are 0. If Configuration.OPTION_FLAG_VOLUME_IN_DBFS has changed the volume curve needs to be set again as the acceptable volume domain has changed.
Returns:the same Builder instance.
/** * @hide * Sets the optional flags * * If omitted, flags are 0. If {@link #OPTION_FLAG_VOLUME_IN_DBFS} has * changed the volume curve needs to be set again as the acceptable * volume domain has changed. * * @param optionFlags new value to replace the old {@code optionFlags}. * @return the same {@code Builder} instance. * @throws IllegalArgumentException if flag is not recognized. */
@TestApi public @NonNull Builder setOptionFlags(@OptionFlag int optionFlags) { if ((optionFlags & ~OPTION_FLAG_PUBLIC_ALL) != 0) { throw new IllegalArgumentException("invalid bits in flag: " + optionFlags); } mOptionFlags = mOptionFlags & ~OPTION_FLAG_PUBLIC_ALL | optionFlags; return this; }
Sets the VolumeShaper duration in milliseconds. If omitted, the default duration is 1 second.
Params:
  • durationMillis –
Throws:
Returns:the same Builder instance.
/** * Sets the {@code VolumeShaper} duration in milliseconds. * * If omitted, the default duration is 1 second. * * @param durationMillis * @return the same {@code Builder} instance. * @throws IllegalArgumentException if {@code durationMillis} * is not strictly positive. */
public @NonNull Builder setDuration(long durationMillis) { if (durationMillis <= 0) { throw new IllegalArgumentException( "duration: " + durationMillis + " not positive"); } mDurationMs = (double) durationMillis; return this; }
Sets the volume curve. The volume curve is represented by a set of control points given by two float arrays of equal length, one representing the time (x) coordinates and one corresponding to the volume (y) coordinates. The length must be at least 2 and no greater than Configuration.getMaximumCurvePoints().

The volume curve is normalized as follows: time (x) coordinates should be monotonically increasing, from 0.f to 1.f; volume (y) coordinates must be within 0.f to 1.f.

The time scale is set by setDuration.

Params:
  • times – an array of float values representing the time line of the volume curve.
  • volumes – an array of float values representing the amplitude of the volume curve.
Throws:
Returns:the same Builder instance.
/** * Sets the volume curve. * * The volume curve is represented by a set of control points given by * two float arrays of equal length, * one representing the time (x) coordinates * and one corresponding to the volume (y) coordinates. * The length must be at least 2 * and no greater than {@link VolumeShaper.Configuration#getMaximumCurvePoints()}. * <p> * The volume curve is normalized as follows: * time (x) coordinates should be monotonically increasing, from 0.f to 1.f; * volume (y) coordinates must be within 0.f to 1.f. * <p> * The time scale is set by {@link #setDuration}. * <p> * @param times an array of float values representing * the time line of the volume curve. * @param volumes an array of float values representing * the amplitude of the volume curve. * @return the same {@code Builder} instance. * @throws IllegalArgumentException if {@code times} or {@code volumes} is invalid. */
/* Note: volume (y) coordinates must be non-positive for log scaling, * if {@link VolumeShaper.Configuration#OPTION_FLAG_VOLUME_IN_DBFS} is set. */ public @NonNull Builder setCurve(@NonNull float[] times, @NonNull float[] volumes) { final boolean log = (mOptionFlags & OPTION_FLAG_VOLUME_IN_DBFS) != 0; checkCurveForErrorsAndThrowException(times, volumes, log, false /* ise */); mTimes = times.clone(); mVolumes = volumes.clone(); return this; }
Reflects the volume curve so that the shaper changes volume from the end to the start.
Throws:
Returns:the same Builder instance.
/** * Reflects the volume curve so that * the shaper changes volume from the end * to the start. * * @return the same {@code Builder} instance. * @throws IllegalStateException if curve has not been set. */
public @NonNull Builder reflectTimes() { final boolean log = (mOptionFlags & OPTION_FLAG_VOLUME_IN_DBFS) != 0; checkCurveForErrorsAndThrowException(mTimes, mVolumes, log, true /* ise */); int i; for (i = 0; i < mTimes.length / 2; ++i) { float temp = mTimes[i]; mTimes[i] = 1.f - mTimes[mTimes.length - 1 - i]; mTimes[mTimes.length - 1 - i] = 1.f - temp; temp = mVolumes[i]; mVolumes[i] = mVolumes[mVolumes.length - 1 - i]; mVolumes[mVolumes.length - 1 - i] = temp; } if ((mTimes.length & 1) != 0) { mTimes[i] = 1.f - mTimes[i]; } return this; }
Inverts the volume curve so that the max volume becomes the min volume and vice versa.
Throws:
Returns:the same Builder instance.
/** * Inverts the volume curve so that the max volume * becomes the min volume and vice versa. * * @return the same {@code Builder} instance. * @throws IllegalStateException if curve has not been set. */
public @NonNull Builder invertVolumes() { final boolean log = (mOptionFlags & OPTION_FLAG_VOLUME_IN_DBFS) != 0; checkCurveForErrorsAndThrowException(mTimes, mVolumes, log, true /* ise */); float min = mVolumes[0]; float max = mVolumes[0]; for (int i = 1; i < mVolumes.length; ++i) { if (mVolumes[i] < min) { min = mVolumes[i]; } else if (mVolumes[i] > max) { max = mVolumes[i]; } } final float maxmin = max + min; for (int i = 0; i < mVolumes.length; ++i) { mVolumes[i] = maxmin - mVolumes[i]; } return this; }
Scale the curve end volume to a target value. Keeps the start volume the same. This works best if the volume curve is monotonic.
Params:
  • volume – the target end volume to use.
Throws:
Returns:the same Builder instance.
/** * Scale the curve end volume to a target value. * * Keeps the start volume the same. * This works best if the volume curve is monotonic. * * @param volume the target end volume to use. * @return the same {@code Builder} instance. * @throws IllegalArgumentException if {@code volume} is not valid. * @throws IllegalStateException if curve has not been set. */
public @NonNull Builder scaleToEndVolume(float volume) { final boolean log = (mOptionFlags & OPTION_FLAG_VOLUME_IN_DBFS) != 0; checkCurveForErrorsAndThrowException(mTimes, mVolumes, log, true /* ise */); checkValidVolumeAndThrowException(volume, log); final float startVolume = mVolumes[0]; final float endVolume = mVolumes[mVolumes.length - 1]; if (endVolume == startVolume) { // match with linear ramp final float offset = volume - startVolume; for (int i = 0; i < mVolumes.length; ++i) { mVolumes[i] = mVolumes[i] + offset * mTimes[i]; } } else { // scale final float scale = (volume - startVolume) / (endVolume - startVolume); for (int i = 0; i < mVolumes.length; ++i) { mVolumes[i] = scale * (mVolumes[i] - startVolume) + startVolume; } } clampVolume(mVolumes, log); return this; }
Scale the curve start volume to a target value. Keeps the end volume the same. This works best if the volume curve is monotonic.
Params:
  • volume – the target start volume to use.
Throws:
Returns:the same Builder instance.
/** * Scale the curve start volume to a target value. * * Keeps the end volume the same. * This works best if the volume curve is monotonic. * * @param volume the target start volume to use. * @return the same {@code Builder} instance. * @throws IllegalArgumentException if {@code volume} is not valid. * @throws IllegalStateException if curve has not been set. */
public @NonNull Builder scaleToStartVolume(float volume) { final boolean log = (mOptionFlags & OPTION_FLAG_VOLUME_IN_DBFS) != 0; checkCurveForErrorsAndThrowException(mTimes, mVolumes, log, true /* ise */); checkValidVolumeAndThrowException(volume, log); final float startVolume = mVolumes[0]; final float endVolume = mVolumes[mVolumes.length - 1]; if (endVolume == startVolume) { // match with linear ramp final float offset = volume - startVolume; for (int i = 0; i < mVolumes.length; ++i) { mVolumes[i] = mVolumes[i] + offset * (1.f - mTimes[i]); } } else { final float scale = (volume - endVolume) / (startVolume - endVolume); for (int i = 0; i < mVolumes.length; ++i) { mVolumes[i] = scale * (mVolumes[i] - endVolume) + endVolume; } } clampVolume(mVolumes, log); return this; }
Builds a new VolumeShaper object.
Throws:
Returns:a new VolumeShaper object.
/** * Builds a new {@link VolumeShaper} object. * * @return a new {@link VolumeShaper} object. * @throws IllegalStateException if curve is not properly set. */
public @NonNull Configuration build() { final boolean log = (mOptionFlags & OPTION_FLAG_VOLUME_IN_DBFS) != 0; checkCurveForErrorsAndThrowException(mTimes, mVolumes, log, true /* ise */); return new Configuration(mType, mId, mOptionFlags, mDurationMs, mInterpolatorType, mTimes, mVolumes); } } // Configuration.Builder } // Configuration
The VolumeShaper.Operation class is used to specify operations to the VolumeShaper that affect the volume change.
/** * The {@code VolumeShaper.Operation} class is used to specify operations * to the {@code VolumeShaper} that affect the volume change. */
public static final class Operation implements Parcelable {
Forward playback from current volume time position. At the end of the VolumeShaper curve, the last volume value persists.
/** * Forward playback from current volume time position. * At the end of the {@code VolumeShaper} curve, * the last volume value persists. */
public static final Operation PLAY = new VolumeShaper.Operation.Builder() .build();
Reverse playback from current volume time position. When the position reaches the start of the VolumeShaper curve, the first volume value persists.
/** * Reverse playback from current volume time position. * When the position reaches the start of the {@code VolumeShaper} curve, * the first volume value persists. */
public static final Operation REVERSE = new VolumeShaper.Operation.Builder() .reverse() .build(); // No user serviceable parts below. // These flags must match the native VolumeShaper::Operation::Flag
@hide
/** @hide */
@IntDef({ FLAG_NONE, FLAG_REVERSE, FLAG_TERMINATE, FLAG_JOIN, FLAG_DEFER, }) @Retention(RetentionPolicy.SOURCE) public @interface Flag {}
No special VolumeShaper operation.
/** * No special {@code VolumeShaper} operation. */
private static final int FLAG_NONE = 0;
Reverse the VolumeShaper progress. Reverses the VolumeShaper curve from its current position. If the VolumeShaper curve has not started, it automatically is considered finished.
/** * Reverse the {@code VolumeShaper} progress. * * Reverses the {@code VolumeShaper} curve from its current * position. If the {@code VolumeShaper} curve has not started, * it automatically is considered finished. */
private static final int FLAG_REVERSE = 1 << 0;
Terminate the existing VolumeShaper. This flag is generally used by itself; it takes precedence over all other flags.
/** * Terminate the existing {@code VolumeShaper}. * This flag is generally used by itself; * it takes precedence over all other flags. */
private static final int FLAG_TERMINATE = 1 << 1;
Attempt to join as best as possible to the previous VolumeShaper. This requires the previous VolumeShaper to be active and setReplaceId to be set.
/** * Attempt to join as best as possible to the previous {@code VolumeShaper}. * This requires the previous {@code VolumeShaper} to be active and * {@link #setReplaceId} to be set. */
private static final int FLAG_JOIN = 1 << 2;
Defer playback until next operation is sent. This is used when starting a VolumeShaper effect.
/** * Defer playback until next operation is sent. This is used * when starting a {@code VolumeShaper} effect. */
private static final int FLAG_DEFER = 1 << 3;
Use the id specified in the configuration, creating VolumeShaper as needed; the configuration should be TYPE_SCALE.
/** * Use the id specified in the configuration, creating * {@code VolumeShaper} as needed; the configuration should be * TYPE_SCALE. */
private static final int FLAG_CREATE_IF_NEEDED = 1 << 4; private static final int FLAG_PUBLIC_ALL = FLAG_REVERSE | FLAG_TERMINATE; private final int mFlags; private final int mReplaceId; private final float mXOffset; @Override public String toString() { return "VolumeShaper.Operation{" + "mFlags = 0x" + Integer.toHexString(mFlags).toUpperCase() + ", mReplaceId = " + mReplaceId + ", mXOffset = " + mXOffset + "}"; } @Override public int hashCode() { return Objects.hash(mFlags, mReplaceId, mXOffset); } @Override public boolean equals(Object o) { if (!(o instanceof Operation)) return false; if (o == this) return true; final Operation other = (Operation) o; return mFlags == other.mFlags && mReplaceId == other.mReplaceId && Float.compare(mXOffset, other.mXOffset) == 0; } @Override public int describeContents() { return 0; } @Override public void writeToParcel(Parcel dest, int flags) { // this needs to match the native VolumeShaper.Operation parceling dest.writeInt(mFlags); dest.writeInt(mReplaceId); dest.writeFloat(mXOffset); } public static final Parcelable.Creator<VolumeShaper.Operation> CREATOR = new Parcelable.Creator<VolumeShaper.Operation>() { @Override public VolumeShaper.Operation createFromParcel(Parcel p) { // this needs to match the native VolumeShaper.Operation parceling final int flags = p.readInt(); final int replaceId = p.readInt(); final float xOffset = p.readFloat(); return new VolumeShaper.Operation( flags , replaceId , xOffset); } @Override public VolumeShaper.Operation[] newArray(int size) { return new VolumeShaper.Operation[size]; } }; private Operation(@Flag int flags, int replaceId, float xOffset) { mFlags = flags; mReplaceId = replaceId; mXOffset = xOffset; }
@hide Builder class for Operation object. Not for public use.
/** * @hide * {@code Builder} class for {@link VolumeShaper.Operation} object. * * Not for public use. */
public static final class Builder { int mFlags; int mReplaceId; float mXOffset;
Constructs a new Builder with the defaults.
/** * Constructs a new {@code Builder} with the defaults. */
public Builder() { mFlags = 0; mReplaceId = -1; mXOffset = Float.NaN; }
Constructs a new Builder from a given VolumeShaper.Operation
Params:
  • operation – the VolumeShaper.operation whose data will be reused in the new Builder.
/** * Constructs a new {@code Builder} from a given {@code VolumeShaper.Operation} * @param operation the {@code VolumeShaper.operation} whose data will be * reused in the new {@code Builder}. */
public Builder(@NonNull VolumeShaper.Operation operation) { mReplaceId = operation.mReplaceId; mFlags = operation.mFlags; mXOffset = operation.mXOffset; }
Replaces the previous VolumeShaper specified by id. The VolumeShaper specified by the id is removed if it exists. The configuration should be TYPE_SCALE.
Params:
  • id – the id of the previous VolumeShaper.
  • join – if true, match the volume of the previous shaper to the start volume of the new VolumeShaper.
Returns:the same Builder instance.
/** * Replaces the previous {@code VolumeShaper} specified by {@code id}. * * The {@code VolumeShaper} specified by the {@code id} is removed * if it exists. The configuration should be TYPE_SCALE. * * @param id the {@code id} of the previous {@code VolumeShaper}. * @param join if true, match the volume of the previous * shaper to the start volume of the new {@code VolumeShaper}. * @return the same {@code Builder} instance. */
public @NonNull Builder replace(int id, boolean join) { mReplaceId = id; if (join) { mFlags |= FLAG_JOIN; } else { mFlags &= ~FLAG_JOIN; } return this; }
Defers all operations.
Returns:the same Builder instance.
/** * Defers all operations. * @return the same {@code Builder} instance. */
public @NonNull Builder defer() { mFlags |= FLAG_DEFER; return this; }
Terminates the VolumeShaper. Do not call directly, use VolumeShaper.close().
Returns:the same Builder instance.
/** * Terminates the {@code VolumeShaper}. * * Do not call directly, use {@link VolumeShaper#close()}. * @return the same {@code Builder} instance. */
public @NonNull Builder terminate() { mFlags |= FLAG_TERMINATE; return this; }
Reverses direction.
Returns:the same Builder instance.
/** * Reverses direction. * @return the same {@code Builder} instance. */
public @NonNull Builder reverse() { mFlags ^= FLAG_REVERSE; return this; }
Use the id specified in the configuration, creating VolumeShaper only as needed; the configuration should be TYPE_SCALE. If the VolumeShaper with the same id already exists then the operation has no effect.
Returns:the same Builder instance.
/** * Use the id specified in the configuration, creating * {@code VolumeShaper} only as needed; the configuration should be * TYPE_SCALE. * * If the {@code VolumeShaper} with the same id already exists * then the operation has no effect. * * @return the same {@code Builder} instance. */
public @NonNull Builder createIfNeeded() { mFlags |= FLAG_CREATE_IF_NEEDED; return this; }
Sets the xOffset to use for the VolumeShaper. The xOffset is the position on the volume curve, and setting takes effect when the VolumeShaper is used next.
Params:
  • xOffset – a value between (or equal to) 0.f and 1.f, or Float.NaN to ignore.
Throws:
Returns:the same Builder instance.
/** * Sets the {@code xOffset} to use for the {@code VolumeShaper}. * * The {@code xOffset} is the position on the volume curve, * and setting takes effect when the {@code VolumeShaper} is used next. * * @param xOffset a value between (or equal to) 0.f and 1.f, or Float.NaN to ignore. * @return the same {@code Builder} instance. * @throws IllegalArgumentException if {@code xOffset} is not between 0.f and 1.f, * or a Float.NaN. */
public @NonNull Builder setXOffset(float xOffset) { if (xOffset < -0.f) { throw new IllegalArgumentException("Negative xOffset not allowed"); } else if (xOffset > 1.f) { throw new IllegalArgumentException("xOffset > 1.f not allowed"); } // Float.NaN passes through mXOffset = xOffset; return this; }
Sets the operation flag. Do not call this directly but one of the other builder methods.
Params:
  • flags – new value for flags, consisting of ORed flags.
Throws:
Returns:the same Builder instance.
/** * Sets the operation flag. Do not call this directly but one of the * other builder methods. * * @param flags new value for {@code flags}, consisting of ORed flags. * @return the same {@code Builder} instance. * @throws IllegalArgumentException if {@code flags} contains invalid set bits. */
private @NonNull Builder setFlags(@Flag int flags) { if ((flags & ~FLAG_PUBLIC_ALL) != 0) { throw new IllegalArgumentException("flag has unknown bits set: " + flags); } mFlags = mFlags & ~FLAG_PUBLIC_ALL | flags; return this; }
Builds a new Operation object.
Returns:a new VolumeShaper.Operation object
/** * Builds a new {@link VolumeShaper.Operation} object. * * @return a new {@code VolumeShaper.Operation} object */
public @NonNull Operation build() { return new Operation(mFlags, mReplaceId, mXOffset); } } // Operation.Builder } // Operation
@hide VolumeShaper.State represents the current progress of the VolumeShaper. Not for public use.
/** * @hide * {@code VolumeShaper.State} represents the current progress * of the {@code VolumeShaper}. * * Not for public use. */
public static final class State implements Parcelable { private float mVolume; private float mXOffset; @Override public String toString() { return "VolumeShaper.State{" + "mVolume = " + mVolume + ", mXOffset = " + mXOffset + "}"; } @Override public int hashCode() { return Objects.hash(mVolume, mXOffset); } @Override public boolean equals(Object o) { if (!(o instanceof State)) return false; if (o == this) return true; final State other = (State) o; return mVolume == other.mVolume && mXOffset == other.mXOffset; } @Override public int describeContents() { return 0; } @Override public void writeToParcel(Parcel dest, int flags) { dest.writeFloat(mVolume); dest.writeFloat(mXOffset); } public static final Parcelable.Creator<VolumeShaper.State> CREATOR = new Parcelable.Creator<VolumeShaper.State>() { @Override public VolumeShaper.State createFromParcel(Parcel p) { return new VolumeShaper.State( p.readFloat() // volume , p.readFloat()); // xOffset } @Override public VolumeShaper.State[] newArray(int size) { return new VolumeShaper.State[size]; } }; /* package */ State(float volume, float xOffset) { mVolume = volume; mXOffset = xOffset; }
Gets the volume of the State.
Returns:linear volume between 0.f and 1.f.
/** * Gets the volume of the {@link VolumeShaper.State}. * @return linear volume between 0.f and 1.f. */
public float getVolume() { return mVolume; }
Gets the xOffset position on the normalized curve of the State.
Returns:the curve x position between 0.f and 1.f.
/** * Gets the {@code xOffset} position on the normalized curve * of the {@link VolumeShaper.State}. * @return the curve x position between 0.f and 1.f. */
public float getXOffset() { return mXOffset; } } // State }