Merge "Make Adaptive Haptics Scaling Linear" into main
diff --git a/core/java/android/os/VibrationEffect.java b/core/java/android/os/VibrationEffect.java
index c9c91fc..efbd96b 100644
--- a/core/java/android/os/VibrationEffect.java
+++ b/core/java/android/os/VibrationEffect.java
@@ -591,9 +591,14 @@
     /**
      * Scale given vibration intensity by the given factor.
      *
+     * <p> 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) {
@@ -624,6 +629,20 @@
     }
 
     /**
+     * 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.
      *
      * @hide
diff --git a/core/java/android/os/vibrator/PrebakedSegment.java b/core/java/android/os/vibrator/PrebakedSegment.java
index a035092..39f8412 100644
--- a/core/java/android/os/vibrator/PrebakedSegment.java
+++ b/core/java/android/os/vibrator/PrebakedSegment.java
@@ -137,6 +137,14 @@
     /** @hide */
     @NonNull
     @Override
+    public PrebakedSegment scaleLinearly(float scaleFactor) {
+        // Prebaked effect strength cannot be scaled with this method.
+        return this;
+    }
+
+    /** @hide */
+    @NonNull
+    @Override
     public PrebakedSegment applyEffectStrength(int effectStrength) {
         if (effectStrength != mEffectStrength && isValidEffectStrength(effectStrength)) {
             return new PrebakedSegment(mEffectId, mFallback, effectStrength);
diff --git a/core/java/android/os/vibrator/PrimitiveSegment.java b/core/java/android/os/vibrator/PrimitiveSegment.java
index 95d97bf..3c84bcd 100644
--- a/core/java/android/os/vibrator/PrimitiveSegment.java
+++ b/core/java/android/os/vibrator/PrimitiveSegment.java
@@ -98,8 +98,24 @@
     @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 5f9d102..09d2e26 100644
--- a/core/java/android/os/vibrator/RampSegment.java
+++ b/core/java/android/os/vibrator/RampSegment.java
@@ -159,6 +159,21 @@
     /** @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
     public RampSegment applyEffectStrength(int effectStrength) {
         return this;
     }
diff --git a/core/java/android/os/vibrator/StepSegment.java b/core/java/android/os/vibrator/StepSegment.java
index 9576a5b..fa083c1 100644
--- a/core/java/android/os/vibrator/StepSegment.java
+++ b/core/java/android/os/vibrator/StepSegment.java
@@ -137,8 +137,25 @@
         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 17ac36f..e1fb4e3 100644
--- a/core/java/android/os/vibrator/VibrationEffectSegment.java
+++ b/core/java/android/os/vibrator/VibrationEffectSegment.java
@@ -96,6 +96,9 @@
     /**
      * Scale the segment intensity with the given factor.
      *
+     * <p> 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
      *
@@ -105,6 +108,17 @@
     public abstract <T extends VibrationEffectSegment> 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 extends VibrationEffectSegment> T scaleLinearly(float scaleFactor);
+
+    /**
      * Applies given effect strength to prebaked effects.
      *
      * @param effectStrength new effect strength to be applied, one of
diff --git a/core/tests/vibrator/src/android/os/vibrator/PrebakedSegmentTest.java b/core/tests/vibrator/src/android/os/vibrator/PrebakedSegmentTest.java
index 4f5f3c0..7dd9e55 100644
--- a/core/tests/vibrator/src/android/os/vibrator/PrebakedSegmentTest.java
+++ b/core/tests/vibrator/src/android/os/vibrator/PrebakedSegmentTest.java
@@ -106,6 +106,13 @@
     }
 
     @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(
                 VibrationEffect.EFFECT_CLICK, true, VibrationEffect.EFFECT_STRENGTH_MEDIUM)
diff --git a/core/tests/vibrator/src/android/os/vibrator/PrimitiveSegmentTest.java b/core/tests/vibrator/src/android/os/vibrator/PrimitiveSegmentTest.java
index ec5a084..e9a08ae 100644
--- a/core/tests/vibrator/src/android/os/vibrator/PrimitiveSegmentTest.java
+++ b/core/tests/vibrator/src/android/os/vibrator/PrimitiveSegmentTest.java
@@ -129,6 +129,27 @@
     }
 
     @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(
                 VibrationEffect.Composition.PRIMITIVE_NOOP, 1, 10).getDuration());
diff --git a/core/tests/vibrator/src/android/os/vibrator/RampSegmentTest.java b/core/tests/vibrator/src/android/os/vibrator/RampSegmentTest.java
index 5caa86b..01013ab 100644
--- a/core/tests/vibrator/src/android/os/vibrator/RampSegmentTest.java
+++ b/core/tests/vibrator/src/android/os/vibrator/RampSegmentTest.java
@@ -131,6 +131,37 @@
     }
 
     @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 44db306..40776ab 100644
--- a/core/tests/vibrator/src/android/os/vibrator/StepSegmentTest.java
+++ b/core/tests/vibrator/src/android/os/vibrator/StepSegmentTest.java
@@ -141,6 +141,37 @@
     }
 
     @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 c9805c7..09a177a 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 @@
                     && mAdaptiveHapticsScales.size() > 0
                     && mAdaptiveHapticsScales.contains(usageHint)) {
                 float adaptiveScale = mAdaptiveHapticsScales.get(usageHint);
-                segment = segment.scale(adaptiveScale);
+                segment = segment.scaleLinearly(adaptiveScale);
             }
 
             segments.set(i, segment);