Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 680034b3 authored by Lais Andrade's avatar Lais Andrade
Browse files

Update haptics scaling

The new scaling is defined as:

(scale_factor * amplitude)
  when scale_factor <= 1

(scale_factor * amplitude) / (1 + (scale_factor - 1) * amplitude^2)
  when scale_factor > 1

Where the scale factor is now defined by a configurable gain between
scale levels. The default value for each scale level is:

 very_low: ~0.5
      low: ~0.7
     none:  1.0
     high:  1.4
very_high: ~2.0

The value is also forwarded to the audio-coupled haptics scaling
function via the ExternalVibrationScale parcelable.

Fix: 356407385
Flag: android.os.vibrator.haptics_scale_v2_enabled
Test: FrameworksVibratorServicesTests
Change-Id: I455d530eb8bf7733d373f6a338411b45b56e9799
parent 006496c2
Loading
Loading
Loading
Loading
+12 −0
Original line number Diff line number Diff line
@@ -33,11 +33,23 @@ parcelable ExternalVibrationScale {
        SCALE_VERY_HIGH = 2
    }

    // TODO(b/345186129): remove this once we finish migrating to scale factor.
    /**
     * The scale level that will be applied to external vibrations.
     */
    ScaleLevel scaleLevel = ScaleLevel.SCALE_NONE;

    /**
     * The scale factor that will be applied to external vibrations.
     *
     * Values in (0,1) will scale down the vibrations, values > 1 will scale up vibrations within
     * hardware limits. A zero scale factor indicates the external vibration should be muted.
     *
     * TODO(b/345186129): update this once we finish migrating, negative should not be expected.
     * Negative values should be ignored in favour of the legacy ScaleLevel.
     */
    float scaleFactor = -1f; // undefined

    /**
     * The adaptive haptics scale that will be applied to external vibrations.
     */
+9 −0
Original line number Diff line number Diff line
@@ -663,6 +663,15 @@ public abstract class VibrationEffect implements Parcelable {
     * @hide
     */
    public static float scale(float intensity, float scaleFactor) {
        if (Flags.hapticsScaleV2Enabled()) {
            if (Float.compare(scaleFactor, 1) <= 0 || Float.compare(intensity, 0) == 0) {
                // Scaling down or scaling zero intensity is straightforward.
                return scaleFactor * intensity;
            }
            // Using S * x / (1 + (S - 1) * x^2) as the scale up function to converge to 1.0.
            return (scaleFactor * intensity) / (1 + (scaleFactor - 1) * intensity * intensity);
        }

        // Applying gamma correction to the scale factor, which is the same as encoding the input
        // value, scaling it, then decoding the scaled value.
        float scale = MathUtils.pow(scaleFactor, 1f / SCALE_GAMMA);
+45 −7
Original line number Diff line number Diff line
@@ -49,8 +49,22 @@ import java.util.Arrays;
 */
public class VibrationConfig {

    /**
     * Hardcoded default scale level gain to be applied between each scale level to define their
     * scale factor value.
     *
     * <p>Default gain defined as 3 dBs.
     */
    private static final float DEFAULT_SCALE_LEVEL_GAIN = 1.4f;

    /**
     * Hardcoded default amplitude to be used when device config is invalid, i.e. not in [1,255].
     */
    private static final int DEFAULT_AMPLITUDE = 255;

    // TODO(b/191150049): move these to vibrator static config file
    private final float mHapticChannelMaxVibrationAmplitude;
    private final int mDefaultVibrationAmplitude;
    private final int mRampStepDurationMs;
    private final int mRampDownDurationMs;
    private final int mRequestVibrationParamsTimeoutMs;
@@ -75,8 +89,10 @@ public class VibrationConfig {

    /** @hide */
    public VibrationConfig(@Nullable Resources resources) {
        mDefaultVibrationAmplitude = resources.getInteger(
                com.android.internal.R.integer.config_defaultVibrationAmplitude);
        mHapticChannelMaxVibrationAmplitude = loadFloat(resources,
                com.android.internal.R.dimen.config_hapticChannelMaxVibrationAmplitude, 0);
                com.android.internal.R.dimen.config_hapticChannelMaxVibrationAmplitude);
        mRampDownDurationMs = loadInteger(resources,
                com.android.internal.R.integer.config_vibrationWaveformRampDownDuration, 0);
        mRampStepDurationMs = loadInteger(resources,
@@ -87,9 +103,9 @@ public class VibrationConfig {
                com.android.internal.R.array.config_requestVibrationParamsForUsages);

        mIgnoreVibrationsOnWirelessCharger = loadBoolean(resources,
                com.android.internal.R.bool.config_ignoreVibrationsOnWirelessCharger, false);
                com.android.internal.R.bool.config_ignoreVibrationsOnWirelessCharger);
        mKeyboardVibrationSettingsSupported = loadBoolean(resources,
                com.android.internal.R.bool.config_keyboardVibrationSettingsSupported, false);
                com.android.internal.R.bool.config_keyboardVibrationSettingsSupported);

        mDefaultAlarmVibrationIntensity = loadDefaultIntensity(resources,
                com.android.internal.R.integer.config_defaultAlarmVibrationIntensity);
@@ -115,16 +131,16 @@ public class VibrationConfig {
        return value;
    }

    private static float loadFloat(@Nullable Resources res, int resId, float defaultValue) {
        return res != null ? res.getFloat(resId) : defaultValue;
    private static float loadFloat(@Nullable Resources res, int resId) {
        return res != null ? res.getFloat(resId) : 0f;
    }

    private static int loadInteger(@Nullable Resources res, int resId, int defaultValue) {
        return res != null ? res.getInteger(resId) : defaultValue;
    }

    private static boolean loadBoolean(@Nullable Resources res, int resId, boolean defaultValue) {
        return res != null ? res.getBoolean(resId) : defaultValue;
    private static boolean loadBoolean(@Nullable Resources res, int resId) {
        return res != null && res.getBoolean(resId);
    }

    private static int[] loadIntArray(@Nullable Resources res, int resId) {
@@ -144,6 +160,26 @@ public class VibrationConfig {
        return mHapticChannelMaxVibrationAmplitude;
    }

    /**
     * Return the device default vibration amplitude value to replace the
     * {@link android.os.VibrationEffect#DEFAULT_AMPLITUDE} constant.
     */
    public int getDefaultVibrationAmplitude() {
        if (mDefaultVibrationAmplitude < 1 || mDefaultVibrationAmplitude > 255) {
            return DEFAULT_AMPLITUDE;
        }
        return mDefaultVibrationAmplitude;
    }

    /**
     * Return the device default gain to be applied between scale levels to define the scale factor
     * for each level.
     */
    public float getDefaultVibrationScaleLevelGain() {
        // TODO(b/356407380): add device config for this
        return DEFAULT_SCALE_LEVEL_GAIN;
    }

    /**
     * The duration, in milliseconds, that should be applied to the ramp to turn off the vibrator
     * when a vibration is cancelled or finished at non-zero amplitude.
@@ -233,6 +269,7 @@ public class VibrationConfig {
    public String toString() {
        return "VibrationConfig{"
                + "mIgnoreVibrationsOnWirelessCharger=" + mIgnoreVibrationsOnWirelessCharger
                + ", mDefaultVibrationAmplitude=" + mDefaultVibrationAmplitude
                + ", mHapticChannelMaxVibrationAmplitude=" + mHapticChannelMaxVibrationAmplitude
                + ", mRampStepDurationMs=" + mRampStepDurationMs
                + ", mRampDownDurationMs=" + mRampDownDurationMs
@@ -258,6 +295,7 @@ public class VibrationConfig {
        pw.println("VibrationConfig:");
        pw.increaseIndent();
        pw.println("ignoreVibrationsOnWirelessCharger = " + mIgnoreVibrationsOnWirelessCharger);
        pw.println("defaultVibrationAmplitude = " + mDefaultVibrationAmplitude);
        pw.println("hapticChannelMaxAmplitude = " + mHapticChannelMaxVibrationAmplitude);
        pw.println("rampStepDurationMs = " + mRampStepDurationMs);
        pw.println("rampDownDurationMs = " + mRampDownDurationMs);
+10 −0
Original line number Diff line number Diff line
@@ -103,3 +103,13 @@ flag {
        purpose: PURPOSE_FEATURE
    }
}

flag {
    namespace: "haptics"
    name: "haptics_scale_v2_enabled"
    description: "Enables new haptics scaling function across all usages"
    bug: "345186129"
    metadata {
        purpose: PURPOSE_FEATURE
    }
}
+43 −2
Original line number Diff line number Diff line
@@ -27,7 +27,11 @@ import android.hardware.vibrator.IVibrator;
import android.os.Parcel;
import android.os.VibrationEffect;
import android.os.VibratorInfo;
import android.platform.test.annotations.DisableFlags;
import android.platform.test.annotations.EnableFlags;
import android.platform.test.flag.junit.SetFlagsRule;

import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
@@ -36,6 +40,9 @@ import org.junit.runners.JUnit4;
public class PrimitiveSegmentTest {
    private static final float TOLERANCE = 1e-2f;

    @Rule
    public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();

    @Test
    public void testCreation() {
        PrimitiveSegment primitive = new PrimitiveSegment(
@@ -87,7 +94,8 @@ public class PrimitiveSegmentTest {
    }

    @Test
    public void testScale_fullPrimitiveScaleValue() {
    @DisableFlags(Flags.FLAG_HAPTICS_SCALE_V2_ENABLED)
    public void testScale_withLegacyScaling_fullPrimitiveScaleValue() {
        PrimitiveSegment initial = new PrimitiveSegment(
                VibrationEffect.Composition.PRIMITIVE_CLICK, 1, 0);

@@ -102,7 +110,24 @@ public class PrimitiveSegmentTest {
    }

    @Test
    public void testScale_halfPrimitiveScaleValue() {
    @EnableFlags(Flags.FLAG_HAPTICS_SCALE_V2_ENABLED)
    public void testScale_withScalingV2_fullPrimitiveScaleValue() {
        PrimitiveSegment initial = new PrimitiveSegment(
                VibrationEffect.Composition.PRIMITIVE_CLICK, 1, 0);

        assertEquals(1f, initial.scale(1).getScale(), TOLERANCE);
        assertEquals(0.5f, initial.scale(0.5f).getScale(), TOLERANCE);
        // The original value was not scaled up, so this only scales it down.
        assertEquals(1f, initial.scale(1.5f).getScale(), TOLERANCE);
        assertEquals(2 / 3f, initial.scale(1.5f).scale(2 / 3f).getScale(), TOLERANCE);
        // Does not restore to the exact original value because scale up is a bit offset.
        assertEquals(0.8f, initial.scale(0.8f).getScale(), TOLERANCE);
        assertEquals(0.86f, initial.scale(0.8f).scale(1.25f).getScale(), TOLERANCE);
    }

    @Test
    @DisableFlags(Flags.FLAG_HAPTICS_SCALE_V2_ENABLED)
    public void testScale_withLegacyScaling_halfPrimitiveScaleValue() {
        PrimitiveSegment initial = new PrimitiveSegment(
                VibrationEffect.Composition.PRIMITIVE_CLICK, 0.5f, 0);

@@ -116,6 +141,22 @@ public class PrimitiveSegmentTest {
        assertEquals(0.5f, initial.scale(0.8f).scale(1.25f).getScale(), TOLERANCE);
    }

    @Test
    @EnableFlags(Flags.FLAG_HAPTICS_SCALE_V2_ENABLED)
    public void testScale_withScalingV2_halfPrimitiveScaleValue() {
        PrimitiveSegment initial = new PrimitiveSegment(
                VibrationEffect.Composition.PRIMITIVE_CLICK, 0.5f, 0);

        assertEquals(0.5f, initial.scale(1).getScale(), TOLERANCE);
        assertEquals(0.25f, initial.scale(0.5f).getScale(), TOLERANCE);
        // The original value was not scaled up, so this only scales it down.
        assertEquals(0.66f, initial.scale(1.5f).getScale(), TOLERANCE);
        assertEquals(0.44f, initial.scale(1.5f).scale(2 / 3f).getScale(), TOLERANCE);
        // Does not restore to the exact original value because scale up is a bit offset.
        assertEquals(0.4f, initial.scale(0.8f).getScale(), TOLERANCE);
        assertEquals(0.48f, initial.scale(0.8f).scale(1.25f).getScale(), TOLERANCE);
    }

    @Test
    public void testScale_zeroPrimitiveScaleValue() {
        PrimitiveSegment initial = new PrimitiveSegment(
Loading