From 5fea2aa6b4c2c9d8941325f424d1cf7ab4078e83 Mon Sep 17 00:00:00 2001 From: Ahmad Khalil Date: Mon, 19 Feb 2024 14:42:14 +0000 Subject: Make Adaptive Haptics Scaling Linear We added a new method to VibrationEffect which will perform linear scaling to vibration intensities. This method is now used to perform the scaling for adaptive haptics and will remove gamma corrections from happening when applying this scale. Bug: 325921278 Test: atest PrebakedSegmentTest/PrimitiveSegmentTest/RampSegmentTest/StepSegmentTest Change-Id: Iea5ece0ec3c01d206462bd3e32de9b299dae54a4 --- core/java/android/os/VibrationEffect.java | 19 +++++++++++++ core/java/android/os/vibrator/PrebakedSegment.java | 8 ++++++ .../java/android/os/vibrator/PrimitiveSegment.java | 20 ++++++++++++-- core/java/android/os/vibrator/RampSegment.java | 15 +++++++++++ core/java/android/os/vibrator/StepSegment.java | 21 +++++++++++++-- .../os/vibrator/VibrationEffectSegment.java | 14 ++++++++++ .../android/os/vibrator/PrebakedSegmentTest.java | 7 +++++ .../android/os/vibrator/PrimitiveSegmentTest.java | 21 +++++++++++++++ .../src/android/os/vibrator/RampSegmentTest.java | 31 ++++++++++++++++++++++ .../src/android/os/vibrator/StepSegmentTest.java | 31 ++++++++++++++++++++++ .../android/server/vibrator/VibrationScaler.java | 2 +- 11 files changed, 184 insertions(+), 5 deletions(-) diff --git a/core/java/android/os/VibrationEffect.java b/core/java/android/os/VibrationEffect.java index c9c91fc49aeb..efbd96bc35cb 100644 --- a/core/java/android/os/VibrationEffect.java +++ b/core/java/android/os/VibrationEffect.java @@ -591,9 +591,14 @@ public abstract class VibrationEffect implements Parcelable { /** * Scale given vibration intensity by the given factor. * + *

This scale is not necessarily linear and may apply a gamma correction to the scale + * factor before using it. + * * @param intensity relative intensity of the effect, must be between 0 and 1 * @param scaleFactor scale factor to be applied to the intensity. Values within [0,1) will * scale down the intensity, values larger than 1 will scale up + * @return the scaled intensity which will be values within [0, 1]. + * * @hide */ public static float scale(float intensity, float scaleFactor) { @@ -623,6 +628,20 @@ public abstract class VibrationEffect implements Parcelable { return MathUtils.constrain(a * fx, 0f, 1f); } + /** + * Performs a linear scaling on the given vibration intensity by the given factor. + * + * @param intensity relative intensity of the effect, must be between 0 and 1. + * @param scaleFactor scale factor to be applied to the intensity. Values within [0,1) will + * scale down the intensity, values larger than 1 will scale up. + * @return the scaled intensity which will be values within [0, 1]. + * + * @hide + */ + public static float scaleLinearly(float intensity, float scaleFactor) { + return MathUtils.constrain(intensity * scaleFactor, 0f, 1f); + } + /** * Returns a compact version of the {@link #toString()} result for debugging purposes. * diff --git a/core/java/android/os/vibrator/PrebakedSegment.java b/core/java/android/os/vibrator/PrebakedSegment.java index a035092e314f..39f841226e4e 100644 --- a/core/java/android/os/vibrator/PrebakedSegment.java +++ b/core/java/android/os/vibrator/PrebakedSegment.java @@ -134,6 +134,14 @@ public final class PrebakedSegment extends VibrationEffectSegment { return this; } + /** @hide */ + @NonNull + @Override + public PrebakedSegment scaleLinearly(float scaleFactor) { + // Prebaked effect strength cannot be scaled with this method. + return this; + } + /** @hide */ @NonNull @Override diff --git a/core/java/android/os/vibrator/PrimitiveSegment.java b/core/java/android/os/vibrator/PrimitiveSegment.java index 95d97bfe4ad1..3c84bcda639b 100644 --- a/core/java/android/os/vibrator/PrimitiveSegment.java +++ b/core/java/android/os/vibrator/PrimitiveSegment.java @@ -98,8 +98,24 @@ public final class PrimitiveSegment extends VibrationEffectSegment { @NonNull @Override public PrimitiveSegment scale(float scaleFactor) { - return new PrimitiveSegment(mPrimitiveId, VibrationEffect.scale(mScale, scaleFactor), - mDelay); + float newScale = VibrationEffect.scale(mScale, scaleFactor); + if (Float.compare(mScale, newScale) == 0) { + return this; + } + + return new PrimitiveSegment(mPrimitiveId, newScale, mDelay); + } + + /** @hide */ + @NonNull + @Override + public PrimitiveSegment scaleLinearly(float scaleFactor) { + float newScale = VibrationEffect.scaleLinearly(mScale, scaleFactor); + if (Float.compare(mScale, newScale) == 0) { + return this; + } + + return new PrimitiveSegment(mPrimitiveId, newScale, mDelay); } /** @hide */ diff --git a/core/java/android/os/vibrator/RampSegment.java b/core/java/android/os/vibrator/RampSegment.java index 5f9d1024d9a5..09d2e26be2a5 100644 --- a/core/java/android/os/vibrator/RampSegment.java +++ b/core/java/android/os/vibrator/RampSegment.java @@ -156,6 +156,21 @@ public final class RampSegment extends VibrationEffectSegment { mDuration); } + /** @hide */ + @NonNull + @Override + public RampSegment scaleLinearly(float scaleFactor) { + float newStartAmplitude = VibrationEffect.scaleLinearly(mStartAmplitude, scaleFactor); + float newEndAmplitude = VibrationEffect.scaleLinearly(mEndAmplitude, scaleFactor); + if (Float.compare(mStartAmplitude, newStartAmplitude) == 0 + && Float.compare(mEndAmplitude, newEndAmplitude) == 0) { + return this; + } + return new RampSegment(newStartAmplitude, newEndAmplitude, mStartFrequencyHz, + mEndFrequencyHz, + mDuration); + } + /** @hide */ @NonNull @Override diff --git a/core/java/android/os/vibrator/StepSegment.java b/core/java/android/os/vibrator/StepSegment.java index 9576a5bba1f1..fa083c176a27 100644 --- a/core/java/android/os/vibrator/StepSegment.java +++ b/core/java/android/os/vibrator/StepSegment.java @@ -137,8 +137,25 @@ public final class StepSegment extends VibrationEffectSegment { if (Float.compare(mAmplitude, VibrationEffect.DEFAULT_AMPLITUDE) == 0) { return this; } - return new StepSegment(VibrationEffect.scale(mAmplitude, scaleFactor), mFrequencyHz, - mDuration); + float newAmplitude = VibrationEffect.scale(mAmplitude, scaleFactor); + if (Float.compare(newAmplitude, mAmplitude) == 0) { + return this; + } + return new StepSegment(newAmplitude, mFrequencyHz, mDuration); + } + + /** @hide */ + @NonNull + @Override + public StepSegment scaleLinearly(float scaleFactor) { + if (Float.compare(mAmplitude, VibrationEffect.DEFAULT_AMPLITUDE) == 0) { + return this; + } + float newAmplitude = VibrationEffect.scaleLinearly(mAmplitude, scaleFactor); + if (Float.compare(newAmplitude, mAmplitude) == 0) { + return this; + } + return new StepSegment(newAmplitude, mFrequencyHz, mDuration); } /** @hide */ diff --git a/core/java/android/os/vibrator/VibrationEffectSegment.java b/core/java/android/os/vibrator/VibrationEffectSegment.java index 17ac36f3ab37..e1fb4e361008 100644 --- a/core/java/android/os/vibrator/VibrationEffectSegment.java +++ b/core/java/android/os/vibrator/VibrationEffectSegment.java @@ -96,6 +96,9 @@ public abstract class VibrationEffectSegment implements Parcelable { /** * Scale the segment intensity with the given factor. * + *

This scale is not necessarily linear and may apply a gamma correction to the scale + * factor before using it. + * * @param scaleFactor scale factor to be applied to the intensity. Values within [0,1) will * scale down the intensity, values larger than 1 will scale up * @@ -104,6 +107,17 @@ public abstract class VibrationEffectSegment implements Parcelable { @NonNull public abstract T scale(float scaleFactor); + /** + * Performs a linear scaling on the segment intensity with the given factor. + * + * @param scaleFactor scale factor to be applied to the intensity. Values within [0,1) will + * scale down the intensity, values larger than 1 will scale up + * + * @hide + */ + @NonNull + public abstract T scaleLinearly(float scaleFactor); + /** * Applies given effect strength to prebaked effects. * diff --git a/core/tests/vibrator/src/android/os/vibrator/PrebakedSegmentTest.java b/core/tests/vibrator/src/android/os/vibrator/PrebakedSegmentTest.java index 4f5f3c0ddeaf..7dd9e55f8f3e 100644 --- a/core/tests/vibrator/src/android/os/vibrator/PrebakedSegmentTest.java +++ b/core/tests/vibrator/src/android/os/vibrator/PrebakedSegmentTest.java @@ -105,6 +105,13 @@ public class PrebakedSegmentTest { assertSame(prebaked, prebaked.scale(0.5f)); } + @Test + public void testScaleLinearly_ignoresAndReturnsSameEffect() { + PrebakedSegment prebaked = new PrebakedSegment( + VibrationEffect.EFFECT_CLICK, true, VibrationEffect.EFFECT_STRENGTH_MEDIUM); + assertSame(prebaked, prebaked.scaleLinearly(0.5f)); + } + @Test public void testDuration() { assertEquals(-1, new PrebakedSegment( diff --git a/core/tests/vibrator/src/android/os/vibrator/PrimitiveSegmentTest.java b/core/tests/vibrator/src/android/os/vibrator/PrimitiveSegmentTest.java index ec5a084c2ddb..e9a08aef4856 100644 --- a/core/tests/vibrator/src/android/os/vibrator/PrimitiveSegmentTest.java +++ b/core/tests/vibrator/src/android/os/vibrator/PrimitiveSegmentTest.java @@ -128,6 +128,27 @@ public class PrimitiveSegmentTest { assertEquals(0f, initial.scale(0.8f).scale(1.25f).getScale(), TOLERANCE); } + @Test + public void testScaleLinearly() { + PrimitiveSegment initial = new PrimitiveSegment( + VibrationEffect.Composition.PRIMITIVE_CLICK, 1, 0); + + assertEquals(1f, initial.scaleLinearly(1).getScale(), TOLERANCE); + assertEquals(0.5f, initial.scaleLinearly(0.5f).getScale(), TOLERANCE); + assertEquals(1f, initial.scaleLinearly(1.5f).getScale(), TOLERANCE); + assertEquals(0.8f, initial.scaleLinearly(0.8f).getScale(), TOLERANCE); + // Restores back to the exact original value since this is a linear scaling. + assertEquals(1f, initial.scaleLinearly(0.8f).scaleLinearly(1.25f).getScale(), TOLERANCE); + + initial = new PrimitiveSegment(VibrationEffect.Composition.PRIMITIVE_CLICK, 0, 0); + + assertEquals(0f, initial.scaleLinearly(1).getScale(), TOLERANCE); + assertEquals(0f, initial.scaleLinearly(0.5f).getScale(), TOLERANCE); + assertEquals(0f, initial.scaleLinearly(1.5f).getScale(), TOLERANCE); + assertEquals(0f, initial.scaleLinearly(1.5f).scaleLinearly(2 / 3f).getScale(), TOLERANCE); + assertEquals(0f, initial.scaleLinearly(0.8f).scaleLinearly(1.25f).getScale(), TOLERANCE); + } + @Test public void testDuration() { assertEquals(-1, new PrimitiveSegment( diff --git a/core/tests/vibrator/src/android/os/vibrator/RampSegmentTest.java b/core/tests/vibrator/src/android/os/vibrator/RampSegmentTest.java index 5caa86bb9fb5..01013ab69f85 100644 --- a/core/tests/vibrator/src/android/os/vibrator/RampSegmentTest.java +++ b/core/tests/vibrator/src/android/os/vibrator/RampSegmentTest.java @@ -130,6 +130,37 @@ public class RampSegmentTest { assertEquals(0.5f, initial.scale(0.8f).scale(1.25f).getStartAmplitude(), TOLERANCE); } + @Test + public void testScaleLinearly() { + RampSegment initial = new RampSegment(0, 1, 0, 0, 0); + + assertEquals(0f, initial.scaleLinearly(1f).getStartAmplitude(), TOLERANCE); + assertEquals(0f, initial.scaleLinearly(0.5f).getStartAmplitude(), TOLERANCE); + assertEquals(0f, initial.scaleLinearly(1.5f).getStartAmplitude(), TOLERANCE); + assertEquals(0f, initial.scaleLinearly(1.5f).scaleLinearly(2 / 3f).getStartAmplitude(), + TOLERANCE); + assertEquals(0f, initial.scaleLinearly(0.8f).scaleLinearly(1.25f).getStartAmplitude(), + TOLERANCE); + + assertEquals(1f, initial.scaleLinearly(1f).getEndAmplitude(), TOLERANCE); + assertEquals(0.5f, initial.scaleLinearly(0.5f).getEndAmplitude(), TOLERANCE); + assertEquals(1f, initial.scaleLinearly(1.5f).getEndAmplitude(), TOLERANCE); + assertEquals(0.8f, initial.scaleLinearly(0.8f).getEndAmplitude(), TOLERANCE); + // Restores back to the exact original value since this is a linear scaling. + assertEquals(0.8f, initial.scaleLinearly(1.5f).scaleLinearly(0.8f).getEndAmplitude(), + TOLERANCE); + + initial = new RampSegment(0.5f, 1, 0, 0, 0); + + assertEquals(0.5f, initial.scaleLinearly(1).getStartAmplitude(), TOLERANCE); + assertEquals(0.25f, initial.scaleLinearly(0.5f).getStartAmplitude(), TOLERANCE); + assertEquals(0.75f, initial.scaleLinearly(1.5f).getStartAmplitude(), TOLERANCE); + assertEquals(0.4f, initial.scaleLinearly(0.8f).getStartAmplitude(), TOLERANCE); + // Restores back to the exact original value since this is a linear scaling. + assertEquals(0.5f, initial.scaleLinearly(0.8f).scaleLinearly(1.25f).getStartAmplitude(), + TOLERANCE); + } + @Test public void testDuration() { assertEquals(10, new RampSegment(0.5f, 1, 0, 0, 10).getDuration()); diff --git a/core/tests/vibrator/src/android/os/vibrator/StepSegmentTest.java b/core/tests/vibrator/src/android/os/vibrator/StepSegmentTest.java index 44db30603089..40776abc0291 100644 --- a/core/tests/vibrator/src/android/os/vibrator/StepSegmentTest.java +++ b/core/tests/vibrator/src/android/os/vibrator/StepSegmentTest.java @@ -140,6 +140,37 @@ public class StepSegmentTest { TOLERANCE); } + @Test + public void testScaleLinearly_fullAmplitude() { + StepSegment initial = new StepSegment(1f, 0, 0); + + assertEquals(1f, initial.scaleLinearly(1).getAmplitude(), TOLERANCE); + assertEquals(0.5f, initial.scaleLinearly(0.5f).getAmplitude(), TOLERANCE); + assertEquals(1f, initial.scaleLinearly(1.5f).getAmplitude(), TOLERANCE); + assertEquals(0.8f, initial.scaleLinearly(0.8f).getAmplitude(), TOLERANCE); + // Restores back to the exact original value since this is a linear scaling. + assertEquals(1f, initial.scaleLinearly(0.8f).scaleLinearly(1.25f).getAmplitude(), + TOLERANCE); + + initial = new StepSegment(0, 0, 0); + + assertEquals(0f, initial.scaleLinearly(1).getAmplitude(), TOLERANCE); + assertEquals(0f, initial.scaleLinearly(0.5f).getAmplitude(), TOLERANCE); + assertEquals(0f, initial.scaleLinearly(1.5f).getAmplitude(), TOLERANCE); + } + + @Test + public void testScaleLinearly_defaultAmplitude() { + StepSegment initial = new StepSegment(VibrationEffect.DEFAULT_AMPLITUDE, 0, 0); + + assertEquals(VibrationEffect.DEFAULT_AMPLITUDE, initial.scaleLinearly(1).getAmplitude(), + TOLERANCE); + assertEquals(VibrationEffect.DEFAULT_AMPLITUDE, initial.scaleLinearly(0.5f).getAmplitude(), + TOLERANCE); + assertEquals(VibrationEffect.DEFAULT_AMPLITUDE, initial.scaleLinearly(1.5f).getAmplitude(), + TOLERANCE); + } + @Test public void testDuration() { assertEquals(5, new StepSegment(0, 0, 5).getDuration()); diff --git a/services/core/java/com/android/server/vibrator/VibrationScaler.java b/services/core/java/com/android/server/vibrator/VibrationScaler.java index c9805c706fbc..09a177a6951b 100644 --- a/services/core/java/com/android/server/vibrator/VibrationScaler.java +++ b/services/core/java/com/android/server/vibrator/VibrationScaler.java @@ -149,7 +149,7 @@ final class VibrationScaler { && mAdaptiveHapticsScales.size() > 0 && mAdaptiveHapticsScales.contains(usageHint)) { float adaptiveScale = mAdaptiveHapticsScales.get(usageHint); - segment = segment.scale(adaptiveScale); + segment = segment.scaleLinearly(adaptiveScale); } segments.set(i, segment); -- cgit v1.2.3-59-g8ed1b