OSDN Git Service

VolumeShaper: Update Builder methods and Object overrides
authorAndy Hung <hunga@google.com>
Mon, 6 Mar 2017 19:39:10 +0000 (11:39 -0800)
committerAndy Hung <hunga@google.com>
Wed, 8 Mar 2017 18:52:26 +0000 (10:52 -0800)
Builder methods now throw IllegalArgumentExceptions on invalid
conditions.

Object hashCode, toString, equals updated for element-wise structural
equivalence.

Test: CTS VolumeShaperTest
Bug: 31015569
Change-Id: I02860a51da58d8207145a9b8a5d2cb13806774b4

media/java/android/media/VolumeShaper.java
media/jni/android_media_VolumeShaper.h

index cb27d10..796d6f3 100644 (file)
@@ -25,6 +25,7 @@ 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;
 
 /**
@@ -115,6 +116,7 @@ public final class VolumeShaper implements AutoCloseable {
      * @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,
@@ -147,6 +149,7 @@ public final class VolumeShaper implements AutoCloseable {
      * Internal call to retrieve the current VolumeShaper state.
      * @param id
      * @return the current {@vode VolumeShaper.State}
+     * @throws IllegalStateException if the player has been deallocated or is uninitialized.
      */
     private @NonNull VolumeShaper.State getStatePlayer(int id) {
         final VolumeShaper.State state;
@@ -172,10 +175,10 @@ public final class VolumeShaper implements AutoCloseable {
      * <p>
      * A {@code VolumeShaper.Configuration} is used by
      * {@link VolumeAutomation#createVolumeShaper(Configuration)
-     * VolumeAutomation#createVolumeShaper(Configuration)} to create
+     * VolumeAutomation.createVolumeShaper(Configuration)} to create
      * a {@code VolumeShaper} and
      * by {@link VolumeShaper#replace(Configuration, Operation, boolean)
-     * VolumeShaper#replace(Configuration, Operation, boolean)}
+     * VolumeShaper.replace(Configuration, Operation, boolean)}
      * to replace an existing {@code configuration}.
      */
     public static final class Configuration implements Parcelable {
@@ -365,31 +368,34 @@ public final class VolumeShaper implements AutoCloseable {
         private final int mId;
 
         // valid when mType is TYPE_SCALE
-        private final int mInterpolatorType;
         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
+            return "VolumeShaper.Configuration{"
+                    + "mType = " + mType
+                    + ", mId = " + mId
                     + (mType == TYPE_ID
-                    ? ",mId" + mId
-                    : ",mInterpolatorType=" + mInterpolatorType
-                    + ",mOptionFlags=" + mOptionFlags
-                    + ",mDurationMs=" + mDurationMs
-                    + ",mTimes[]=" + mTimes
-                    + ",mVolumes[]=" + mVolumes
-                    + "]");
+                        ? "}"
+                        : ", 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, mInterpolatorType, mDurationMs, mTimes, mVolumes);
+                    : Objects.hash(mType, mId,
+                            mOptionFlags, mDurationMs, mInterpolatorType,
+                            Arrays.hashCode(mTimes), Arrays.hashCode(mVolumes));
         }
 
         @Override
@@ -397,12 +403,17 @@ public final class VolumeShaper implements AutoCloseable {
             if (!(o instanceof Configuration)) return false;
             if (o == this) return true;
             final Configuration other = (Configuration) o;
-            return mType == other.mType &&
-                    (mType == TYPE_ID ? mId == other.mId
-                    : mInterpolatorType == other.mInterpolatorType
-                    && mDurationMs == other.mDurationMs
-                    && mTimes == other.mTimes
-                    && mVolumes == other.mVolumes);
+            // 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
@@ -412,14 +423,22 @@ public final class VolumeShaper implements AutoCloseable {
 
         @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(mInterpolatorType);
                 dest.writeInt(mOptionFlags);
                 dest.writeDouble(mDurationMs);
-                dest.writeFloatArray(mTimes);
-                dest.writeFloatArray(mVolumes);
+                // this needs to match the native Interpolator parceling
+                dest.writeInt(mInterpolatorType);
+                dest.writeFloat(0.f); // first slope
+                dest.writeFloat(0.f); // last slope
+                // 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]);
+                }
             }
         }
 
@@ -427,19 +446,34 @@ public final class VolumeShaper implements AutoCloseable {
                 = 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
+                    final float lastSlope = p.readFloat();  // ignored
+                    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,                    // id
-                        p.readInt(),           // interpolatorType
-                        p.readInt(),           // optionFlags
-                        p.readDouble(),        // durationMs
-                        p.createFloatArray(),  // times
-                        p.createFloatArray()); // volumes
+                        id,
+                        optionFlags,
+                        durationMs,
+                        interpolatorType,
+                        times,
+                        volumes);
                 }
             }
 
@@ -482,16 +516,16 @@ public final class VolumeShaper implements AutoCloseable {
          */
         private Configuration(@Type int type,
                 int id,
-                @InterpolatorType int interpolatorType,
                 @OptionFlag int optionFlags,
                 double durationMs,
+                @InterpolatorType int interpolatorType,
                 @NonNull float[] times,
                 @NonNull float[] volumes) {
             mType = type;
             mId = id;
-            mInterpolatorType = interpolatorType;
             mOptionFlags = optionFlags;
             mDurationMs = durationMs;
+            mInterpolatorType = interpolatorType;
             // Builder should have cloned these arrays already.
             mTimes = times;
             mVolumes = volumes;
@@ -568,8 +602,12 @@ public final class VolumeShaper implements AutoCloseable {
          * @return null if no error, or the reason in a {@code String} for an error.
          */
         private static @Nullable String checkCurveForErrors(
-                @NonNull float[] times, @NonNull float[] volumes, boolean log) {
-            if (times.length != volumes.length) {
+                @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";
@@ -605,7 +643,15 @@ public final class VolumeShaper implements AutoCloseable {
             return null; // no errors
         }
 
-        private static void checkValidVolume(float volume, boolean log) {
+        private static void checkCurveForErrorsAndThrowException(
+                @Nullable float[] times, @Nullable float[] volumes, boolean log) {
+            final String error = checkCurveForErrors(times, volumes, log);
+            if (error != null) {
+                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");
@@ -678,17 +724,22 @@ public final class VolumeShaper implements AutoCloseable {
                 mOptionFlags = configuration.getAllOptionFlags();
                 mInterpolatorType = configuration.getInterpolatorType();
                 mDurationMs = configuration.getDurationMs();
-                mTimes = configuration.getTimes();
-                mVolumes = configuration.getVolumes();
+                mTimes = configuration.getTimes().clone();
+                mVolumes = configuration.getVolumes().clone();
             }
 
             /**
              * @hide
-             * Set the id for system defined shapers.
-             * @param id
-             * @return
+             * 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;
             }
@@ -789,11 +840,8 @@ public final class VolumeShaper implements AutoCloseable {
              */
 
             public @NonNull Builder setCurve(@NonNull float[] times, @NonNull float[] volumes) {
-                String error = checkCurveForErrors(
+                checkCurveForErrorsAndThrowException(
                         times, volumes, (mOptionFlags & OPTION_FLAG_VOLUME_IN_DBFS) != 0);
-                if (error != null) {
-                    throw new IllegalArgumentException(error);
-                }
                 mTimes = times.clone();
                 mVolumes = volumes.clone();
                 return this;
@@ -805,13 +853,19 @@ public final class VolumeShaper implements AutoCloseable {
              * to the start.
              *
              * @return the same {@code Builder} instance.
+             * @throws IllegalArgumentException if curve has not been set.
              */
             public @NonNull Builder reflectTimes() {
+                checkCurveForErrorsAndThrowException(
+                        mTimes, mVolumes, (mOptionFlags & OPTION_FLAG_VOLUME_IN_DBFS) != 0);
                 int i;
                 for (i = 0; i < mTimes.length / 2; ++i) {
-                    float temp = mTimes[0];
+                    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];
@@ -824,23 +878,24 @@ public final class VolumeShaper implements AutoCloseable {
              * becomes the min volume and vice versa.
              *
              * @return the same {@code Builder} instance.
+             * @throws IllegalArgumentException if curve has not been set.
              */
             public @NonNull Builder invertVolumes() {
-                if (mVolumes.length >= 2) {
-                    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];
-                        }
+                checkCurveForErrorsAndThrowException(
+                        mTimes, mVolumes, (mOptionFlags & OPTION_FLAG_VOLUME_IN_DBFS) != 0);
+                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];
-                    }
+                final float maxmin = max + min;
+                for (int i = 0; i < mVolumes.length; ++i) {
+                    mVolumes[i] = maxmin - mVolumes[i];
                 }
                 return this;
             }
@@ -853,11 +908,13 @@ public final class VolumeShaper implements AutoCloseable {
              *
              * @param volume the target end volume to use.
              * @return the same {@code Builder} instance.
-             * @throws IllegalArgumentException if {@code volume} is not valid.
+             * @throws IllegalArgumentException if {@code volume}
+             *         is not valid or if curve has not been set.
              */
             public @NonNull Builder scaleToEndVolume(float volume) {
                 final boolean log = (mOptionFlags & OPTION_FLAG_VOLUME_IN_DBFS) != 0;
-                checkValidVolume(volume, log);
+                checkCurveForErrorsAndThrowException(mTimes, mVolumes, log);
+                checkValidVolumeAndThrowException(volume, log);
                 final float startVolume = mVolumes[0];
                 final float endVolume = mVolumes[mVolumes.length - 1];
                 if (endVolume == startVolume) {
@@ -885,11 +942,13 @@ public final class VolumeShaper implements AutoCloseable {
              *
              * @param volume the target start volume to use.
              * @return the same {@code Builder} instance.
-             * @throws IllegalArgumentException if {@code volume} is not valid.
+             * @throws IllegalArgumentException if {@code volume}
+             *         is not valid or if curve has not been set.
              */
             public @NonNull Builder scaleToStartVolume(float volume) {
                 final boolean log = (mOptionFlags & OPTION_FLAG_VOLUME_IN_DBFS) != 0;
-                checkValidVolume(volume, log);
+                checkCurveForErrorsAndThrowException(mTimes, mVolumes, log);
+                checkValidVolumeAndThrowException(volume, log);
                 final float startVolume = mVolumes[0];
                 final float endVolume = mVolumes[mVolumes.length - 1];
                 if (endVolume == startVolume) {
@@ -911,16 +970,14 @@ public final class VolumeShaper implements AutoCloseable {
             /**
              * Builds a new {@link VolumeShaper} object.
              *
-             * @return a new {@link VolumeShaper} object
+             * @return a new {@link VolumeShaper} object.
+             * @throws IllegalArgumentException if curve is not properly set.
              */
             public @NonNull Configuration build() {
-                String error = checkCurveForErrors(
+                checkCurveForErrorsAndThrowException(
                         mTimes, mVolumes, (mOptionFlags & OPTION_FLAG_VOLUME_IN_DBFS) != 0);
-                if (error != null) {
-                    throw new IllegalArgumentException(error);
-                }
-                return new Configuration(mType, mId, mInterpolatorType, mOptionFlags,
-                        mDurationMs, mTimes, mVolumes);
+                return new Configuration(mType, mId, mOptionFlags, mDurationMs,
+                        mInterpolatorType, mTimes, mVolumes);
             }
         } // Configuration.Builder
     } // Configuration
@@ -1011,10 +1068,10 @@ public final class VolumeShaper implements AutoCloseable {
 
         @Override
         public String toString() {
-            return "VolumeShaper.Operation["
-                    + "mFlags=" + mFlags
-                    + ",mReplaceId" + mReplaceId
-                    + "]";
+            return "VolumeShaper.Operation{"
+                    + "mFlags = 0x" + Integer.toHexString(mFlags).toUpperCase()
+                    + ", mReplaceId = " + mReplaceId
+                    + "}";
         }
 
         @Override
@@ -1027,6 +1084,8 @@ public final class VolumeShaper implements AutoCloseable {
             if (!(o instanceof Operation)) return false;
             if (o == this) return true;
             final Operation other = (Operation) o;
+            // if xOffset (native field only) is brought into Java
+            // we need to do proper NaN comparison as that is allowed.
             return mFlags == other.mFlags
                     && mReplaceId == other.mReplaceId;
         }
@@ -1038,17 +1097,24 @@ public final class VolumeShaper implements AutoCloseable {
 
         @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(Float.NaN); // xOffset (ignored at Java level)
         }
 
         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(); // ignored at Java level
+
                 return new VolumeShaper.Operation(
-                        p.readInt()     // flags
-                        , p.readInt()); // replaceId
+                        flags
+                        , replaceId);
             }
 
             @Override
@@ -1154,6 +1220,7 @@ public final class VolumeShaper implements AutoCloseable {
              *
              * @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) {
@@ -1187,10 +1254,10 @@ public final class VolumeShaper implements AutoCloseable {
 
         @Override
         public String toString() {
-            return "VolumeShaper.State["
-                    + "mVolume=" + mVolume
-                    + ",mXOffset" + mXOffset
-                    + "]";
+            return "VolumeShaper.State{"
+                    + "mVolume = " + mVolume
+                    + ", mXOffset = " + mXOffset
+                    + "}";
         }
 
         @Override
index dbbc478..73498a2 100644 (file)
@@ -29,9 +29,9 @@ struct VolumeShaperHelper {
         jmethodID coConstructId;
         jfieldID  coTypeId;
         jfieldID  coIdId;
-        jfieldID  coInterpolatorTypeId;
         jfieldID  coOptionFlagsId;
         jfieldID  coDurationMsId;
+        jfieldID  coInterpolatorTypeId;
         jfieldID  coTimesId;
         jfieldID  coVolumesId;
 
@@ -56,12 +56,12 @@ struct VolumeShaperHelper {
             if (coClazz == nullptr) {
                 return;
             }
-            coConstructId = env->GetMethodID(coClazz, "<init>", "(IIIID[F[F)V");
+            coConstructId = env->GetMethodID(coClazz, "<init>", "(IIIDI[F[F)V");
             coTypeId = env->GetFieldID(coClazz, "mType", "I");
             coIdId = env->GetFieldID(coClazz, "mId", "I");
-            coInterpolatorTypeId = env->GetFieldID(coClazz, "mInterpolatorType", "I");
             coOptionFlagsId = env->GetFieldID(coClazz, "mOptionFlags", "I");
             coDurationMsId = env->GetFieldID(coClazz, "mDurationMs", "D");
+            coInterpolatorTypeId = env->GetFieldID(coClazz, "mInterpolatorType", "I");
             coTimesId = env->GetFieldID(coClazz, "mTimes", "[F");
             coVolumesId = env->GetFieldID(coClazz, "mVolumes", "[F");
             env->DeleteLocalRef(lclazz);
@@ -108,14 +108,14 @@ struct VolumeShaperHelper {
         configuration->setId(
             (int)env->GetIntField(jshaper, fields.coIdId));
         if (configuration->getType() == VolumeShaper::Configuration::TYPE_SCALE) {
-            configuration->setInterpolatorType(
-                (VolumeShaper::Configuration::InterpolatorType)
-                env->GetIntField(jshaper, fields.coInterpolatorTypeId));
             configuration->setOptionFlags(
                 (VolumeShaper::Configuration::OptionFlag)
                 env->GetIntField(jshaper, fields.coOptionFlagsId));
             configuration->setDurationMs(
                     (double)env->GetDoubleField(jshaper, fields.coDurationMsId));
+            configuration->setInterpolatorType(
+                (VolumeShaper::Configuration::InterpolatorType)
+                env->GetIntField(jshaper, fields.coInterpolatorTypeId));
 
             // convert point arrays
             jobject xobj = env->GetObjectField(jshaper, fields.coTimesId);
@@ -165,9 +165,9 @@ struct VolumeShaperHelper {
         jvalue args[7];
         args[0].i = (jint)configuration->getType();
         args[1].i = (jint)configuration->getId();
-        args[2].i = (jint)configuration->getInterpolatorType();
-        args[3].i = (jint)configuration->getOptionFlags();
-        args[4].d = (jdouble)configuration->getDurationMs();
+        args[2].i = (jint)configuration->getOptionFlags();
+        args[3].d = (jdouble)configuration->getDurationMs();
+        args[4].i = (jint)configuration->getInterpolatorType();
         args[5].l = xarray;
         args[6].l = yarray;
         jobject jshaper = env->NewObjectA(fields.coClazz, fields.coConstructId, args);