From 28b177ce6750afc9f523cbfec461d4926ef5b7be Mon Sep 17 00:00:00 2001 From: Lais Andrade Date: Wed, 14 Jun 2023 14:15:54 +0100 Subject: [PATCH] Refactor DeviceAdapter to extract split-ramp logic This change updates the DeviceAdapter implementation to clear out the step-to-ramp and ramp-to-step adapters and move the split ramp logic to a separate adapter class. This will make sure that long PWLE segments will always be split, even if they're added by other adapters like the ramp-down one that has duration configured independently from the HAL capabilities. Fix: 292081642 Test: atest SplitSegmentsAdapterTest Change-Id: I091e539a516985fdcf90a4e1f4d8c9dc391eb772 --- core/java/android/os/VibrationEffect.java | 14 +- .../android/os/vibrator/PrebakedSegment.java | 6 - .../android/os/vibrator/PrimitiveSegment.java | 7 - .../java/android/os/vibrator/RampSegment.java | 6 - .../java/android/os/vibrator/StepSegment.java | 7 - .../os/vibrator/VibrationEffectSegment.java | 7 - .../os/vibrator/PrebakedSegmentTest.java | 1 - .../os/vibrator/PrimitiveSegmentTest.java | 1 - .../android/os/vibrator/RampSegmentTest.java | 8 - .../android/os/vibrator/StepSegmentTest.java | 9 -- .../ClippingAmplitudeAndFrequencyAdapter.java | 6 +- .../server/vibrator/DeviceAdapter.java | 5 + .../server/vibrator/RampToStepAdapter.java | 10 +- .../server/vibrator/SplitSegmentsAdapter.java | 115 ++++++++++++++ .../server/vibrator/StepToRampAdapter.java | 82 +--------- .../vibrator/RampToStepAdapterTest.java | 3 + .../vibrator/SplitSegmentsAdapterTest.java | 144 ++++++++++++++++++ .../vibrator/StepToRampAdapterTest.java | 36 +---- 18 files changed, 291 insertions(+), 176 deletions(-) create mode 100644 services/core/java/com/android/server/vibrator/SplitSegmentsAdapter.java create mode 100644 services/tests/vibrator/src/com/android/server/vibrator/SplitSegmentsAdapterTest.java diff --git a/core/java/android/os/VibrationEffect.java b/core/java/android/os/VibrationEffect.java index 0461b2eb413b6..c3d7540b8cb2f 100644 --- a/core/java/android/os/VibrationEffect.java +++ b/core/java/android/os/VibrationEffect.java @@ -232,11 +232,11 @@ public abstract class VibrationEffect implements Parcelable { * Computes a legacy vibration pattern (i.e. a pattern with duration values for "off/on" * vibration components) that is equivalent to this VibrationEffect. * - *

All non-repeating effects created with {@link #createWaveform(int[], int)} are convertible - * into an equivalent vibration pattern with this method. It is not guaranteed that an effect - * created with other means becomes converted into an equivalent legacy vibration pattern, even - * if it has an equivalent vibration pattern. If this method is unable to create an equivalent - * vibration pattern for such effects, it will return {@code null}. + *

All non-repeating effects created with {@link #createWaveform(long[], int)} are + * convertible into an equivalent vibration pattern with this method. It is not guaranteed that + * an effect created with other means becomes converted into an equivalent legacy vibration + * pattern, even if it has an equivalent vibration pattern. If this method is unable to create + * an equivalent vibration pattern for such effects, it will return {@code null}. * *

Note that a valid equivalent long[] pattern cannot be created for an effect that has any * form of repeating behavior, regardless of how the effect was created. For repeating effects, @@ -245,7 +245,7 @@ public abstract class VibrationEffect implements Parcelable { * @return a long array representing a vibration pattern equivalent to the VibrationEffect, if * the method successfully derived a vibration pattern equivalent to the effect * (this will always be the case if the effect was created via - * {@link #createWaveform(int[], int)} and is non-repeating). Otherwise, returns + * {@link #createWaveform(long[], int)} and is non-repeating). Otherwise, returns * {@code null}. * @hide */ @@ -953,7 +953,7 @@ public abstract class VibrationEffect implements Parcelable { /** * Casts a provided {@link VibrationEffectSegment} to a {@link StepSegment} and returns it, * only if it can possibly be a segment for an effect created via - * {@link #createWaveform(int[], int)}. Otherwise, returns {@code null}. + * {@link #createWaveform(long[], int)}. Otherwise, returns {@code null}. */ @Nullable private static StepSegment castToValidStepSegmentForOffOnTimingsOrNull( diff --git a/core/java/android/os/vibrator/PrebakedSegment.java b/core/java/android/os/vibrator/PrebakedSegment.java index da2ee6c756098..462e5add9d33e 100644 --- a/core/java/android/os/vibrator/PrebakedSegment.java +++ b/core/java/android/os/vibrator/PrebakedSegment.java @@ -118,12 +118,6 @@ public final class PrebakedSegment extends VibrationEffectSegment { } } - /** @hide */ - @Override - public boolean hasNonZeroAmplitude() { - return true; - } - /** @hide */ @NonNull @Override diff --git a/core/java/android/os/vibrator/PrimitiveSegment.java b/core/java/android/os/vibrator/PrimitiveSegment.java index e1fa97b34bd1f..815a1dc363e3c 100644 --- a/core/java/android/os/vibrator/PrimitiveSegment.java +++ b/core/java/android/os/vibrator/PrimitiveSegment.java @@ -87,13 +87,6 @@ public final class PrimitiveSegment extends VibrationEffectSegment { return true; } - /** @hide */ - @Override - public boolean hasNonZeroAmplitude() { - // Every primitive plays a vibration with a non-zero amplitude, even at scale == 0. - return true; - } - /** @hide */ @NonNull @Override diff --git a/core/java/android/os/vibrator/RampSegment.java b/core/java/android/os/vibrator/RampSegment.java index 034962a5f91b1..a5dee6c8e5bcd 100644 --- a/core/java/android/os/vibrator/RampSegment.java +++ b/core/java/android/os/vibrator/RampSegment.java @@ -123,12 +123,6 @@ public final class RampSegment extends VibrationEffectSegment { return true; } - /** @hide */ - @Override - public boolean hasNonZeroAmplitude() { - return mStartAmplitude > 0 || mEndAmplitude > 0; - } - /** @hide */ @Override public void validate() { diff --git a/core/java/android/os/vibrator/StepSegment.java b/core/java/android/os/vibrator/StepSegment.java index 817187e1cebb8..54a44a88b7521 100644 --- a/core/java/android/os/vibrator/StepSegment.java +++ b/core/java/android/os/vibrator/StepSegment.java @@ -99,13 +99,6 @@ public final class StepSegment extends VibrationEffectSegment { return true; } - /** @hide */ - @Override - public boolean hasNonZeroAmplitude() { - // DEFAULT_AMPLITUDE == -1 is still a non-zero amplitude that will be resolved later. - return Float.compare(mAmplitude, 0) != 0; - } - /** @hide */ @Override public void validate() { diff --git a/core/java/android/os/vibrator/VibrationEffectSegment.java b/core/java/android/os/vibrator/VibrationEffectSegment.java index 75a055fa5273b..9a19ed417f497 100644 --- a/core/java/android/os/vibrator/VibrationEffectSegment.java +++ b/core/java/android/os/vibrator/VibrationEffectSegment.java @@ -75,13 +75,6 @@ public abstract class VibrationEffectSegment implements Parcelable { */ public abstract boolean isHapticFeedbackCandidate(); - /** - * Returns true if this segment plays at a non-zero amplitude at some point. - * - * @hide - */ - public abstract boolean hasNonZeroAmplitude(); - /** * Validates the segment, throwing exceptions if any parameter is invalid. * diff --git a/core/tests/vibrator/src/android/os/vibrator/PrebakedSegmentTest.java b/core/tests/vibrator/src/android/os/vibrator/PrebakedSegmentTest.java index 32311923e3d10..8268077e72e9d 100644 --- a/core/tests/vibrator/src/android/os/vibrator/PrebakedSegmentTest.java +++ b/core/tests/vibrator/src/android/os/vibrator/PrebakedSegmentTest.java @@ -45,7 +45,6 @@ public class PrebakedSegmentTest { VibrationEffect.EFFECT_CLICK, true, VibrationEffect.EFFECT_STRENGTH_MEDIUM); assertEquals(-1, prebaked.getDuration()); - assertTrue(prebaked.hasNonZeroAmplitude()); assertEquals(VibrationEffect.EFFECT_CLICK, prebaked.getEffectId()); assertEquals(VibrationEffect.EFFECT_STRENGTH_MEDIUM, prebaked.getEffectStrength()); assertTrue(prebaked.shouldFallback()); diff --git a/core/tests/vibrator/src/android/os/vibrator/PrimitiveSegmentTest.java b/core/tests/vibrator/src/android/os/vibrator/PrimitiveSegmentTest.java index 955d6acb88826..6f5adcd26ba5d 100644 --- a/core/tests/vibrator/src/android/os/vibrator/PrimitiveSegmentTest.java +++ b/core/tests/vibrator/src/android/os/vibrator/PrimitiveSegmentTest.java @@ -46,7 +46,6 @@ public class PrimitiveSegmentTest { VibrationEffect.Composition.PRIMITIVE_CLICK, 1, 10); assertEquals(-1, primitive.getDuration()); - assertTrue(primitive.hasNonZeroAmplitude()); assertEquals(VibrationEffect.Composition.PRIMITIVE_CLICK, primitive.getPrimitiveId()); assertEquals(10, primitive.getDelay()); assertEquals(1f, primitive.getScale(), TOLERANCE); diff --git a/core/tests/vibrator/src/android/os/vibrator/RampSegmentTest.java b/core/tests/vibrator/src/android/os/vibrator/RampSegmentTest.java index dcbb56e22c77d..68870e5a29797 100644 --- a/core/tests/vibrator/src/android/os/vibrator/RampSegmentTest.java +++ b/core/tests/vibrator/src/android/os/vibrator/RampSegmentTest.java @@ -54,7 +54,6 @@ public class RampSegmentTest { /* startFrequencyHz= */ 100, /* endFrequencyHz= */ 200, /* duration= */ 100); assertEquals(100L, ramp.getDuration()); - assertTrue(ramp.hasNonZeroAmplitude()); assertEquals(1f, ramp.getStartAmplitude()); assertEquals(0f, ramp.getEndAmplitude()); assertEquals(100f, ramp.getStartFrequencyHz()); @@ -95,13 +94,6 @@ public class RampSegmentTest { () -> new RampSegment(0, 0, /* startFrequencyHz= */ Float.NaN, 0, 0).validate()); } - @Test - public void testHasNonZeroAmplitude() { - assertTrue(new RampSegment(0, 1, 0, 0, 0).hasNonZeroAmplitude()); - assertTrue(new RampSegment(0.01f, 0, 0, 0, 0).hasNonZeroAmplitude()); - assertFalse(new RampSegment(0, 0, 0, 0, 0).hasNonZeroAmplitude()); - } - @Test public void testResolve() { RampSegment ramp = new RampSegment(0, 1, 0, 0, 0); diff --git a/core/tests/vibrator/src/android/os/vibrator/StepSegmentTest.java b/core/tests/vibrator/src/android/os/vibrator/StepSegmentTest.java index f9f1c08ba60d0..34bb892b07d38 100644 --- a/core/tests/vibrator/src/android/os/vibrator/StepSegmentTest.java +++ b/core/tests/vibrator/src/android/os/vibrator/StepSegmentTest.java @@ -52,7 +52,6 @@ public class StepSegmentTest { /* duration= */ 100); assertEquals(100, step.getDuration()); - assertTrue(step.hasNonZeroAmplitude()); assertEquals(1f, step.getAmplitude()); assertEquals(1f, step.getFrequencyHz()); } @@ -86,14 +85,6 @@ public class StepSegmentTest { () -> new StepSegment(1, /* frequencyHz*/ Float.NaN, 10).validate()); } - @Test - public void testHasNonZeroAmplitude() { - assertTrue(new StepSegment(1f, 0, 0).hasNonZeroAmplitude()); - assertTrue(new StepSegment(0.01f, 0, 0).hasNonZeroAmplitude()); - assertTrue(new StepSegment(VibrationEffect.DEFAULT_AMPLITUDE, 0, 0).hasNonZeroAmplitude()); - assertFalse(new StepSegment(0, 0, 0).hasNonZeroAmplitude()); - } - @Test public void testResolve() { StepSegment original = new StepSegment(VibrationEffect.DEFAULT_AMPLITUDE, 0, 0); diff --git a/services/core/java/com/android/server/vibrator/ClippingAmplitudeAndFrequencyAdapter.java b/services/core/java/com/android/server/vibrator/ClippingAmplitudeAndFrequencyAdapter.java index 48d477c86220f..111e075a73747 100644 --- a/services/core/java/com/android/server/vibrator/ClippingAmplitudeAndFrequencyAdapter.java +++ b/services/core/java/com/android/server/vibrator/ClippingAmplitudeAndFrequencyAdapter.java @@ -30,7 +30,7 @@ import java.util.List; * each frequency. * *

The {@link VibratorInfo.FrequencyProfile} is only applicable to PWLE compositions. This - * adapter is only applied to {@link RampSegment} and leaves all other segments unchanged. + * adapter is only applied to {@link RampSegment} and all other segments will remain unchanged. */ final class ClippingAmplitudeAndFrequencyAdapter implements VibrationSegmentsAdapter { @@ -41,13 +41,13 @@ final class ClippingAmplitudeAndFrequencyAdapter implements VibrationSegmentsAda for (int i = 0; i < segmentCount; i++) { VibrationEffectSegment segment = segments.get(i); if (segment instanceof RampSegment) { - segments.set(i, apply((RampSegment) segment, info)); + segments.set(i, adaptToVibrator(info, (RampSegment) segment)); } } return repeatIndex; } - private RampSegment apply(RampSegment segment, VibratorInfo info) { + private RampSegment adaptToVibrator(VibratorInfo info, RampSegment segment) { float clampedStartFrequency = clampFrequency(info, segment.getStartFrequencyHz()); float clampedEndFrequency = clampFrequency(info, segment.getEndFrequencyHz()); return new RampSegment( diff --git a/services/core/java/com/android/server/vibrator/DeviceAdapter.java b/services/core/java/com/android/server/vibrator/DeviceAdapter.java index 41649fac54f01..98309cd007580 100644 --- a/services/core/java/com/android/server/vibrator/DeviceAdapter.java +++ b/services/core/java/com/android/server/vibrator/DeviceAdapter.java @@ -53,9 +53,14 @@ final class DeviceAdapter implements CombinedVibration.VibratorAdapter { mSegmentAdapters = Arrays.asList( // TODO(b/167947076): add filter that removes unsupported primitives // TODO(b/167947076): add filter that replaces unsupported prebaked with fallback + // Convert segments based on device capabilities new RampToStepAdapter(settings.getRampStepDuration()), new StepToRampAdapter(), + // Add extra ramp down segments as needed new RampDownAdapter(settings.getRampDownDuration(), settings.getRampStepDuration()), + // Split segments based on their duration and device supported limits + new SplitSegmentsAdapter(), + // Clip amplitudes and frequencies of final segments based on device bandwidth curve new ClippingAmplitudeAndFrequencyAdapter() ); mAvailableVibrators = vibrators; diff --git a/services/core/java/com/android/server/vibrator/RampToStepAdapter.java b/services/core/java/com/android/server/vibrator/RampToStepAdapter.java index 9e248cd5effcf..fc19e27ed92c8 100644 --- a/services/core/java/com/android/server/vibrator/RampToStepAdapter.java +++ b/services/core/java/com/android/server/vibrator/RampToStepAdapter.java @@ -28,9 +28,11 @@ import java.util.Arrays; import java.util.List; /** - * Adapter that converts ramp segments that to a sequence of fixed step segments. + * Adapter that converts ramp segments to a sequence of fixed step segments. * - *

This leaves the list unchanged if the device has compose PWLE capability. + *

This change preserves the frequency parameters by interpolating the ramp values. + * + *

The segments will not be changed if the device has {@link IVibrator#CAP_COMPOSE_PWLE_EFFECTS}. */ final class RampToStepAdapter implements VibrationSegmentsAdapter { @@ -53,7 +55,7 @@ final class RampToStepAdapter implements VibrationSegmentsAdapter { if (!(segment instanceof RampSegment)) { continue; } - List steps = apply(info, (RampSegment) segment); + List steps = convertRampToSteps(info, (RampSegment) segment); segments.remove(i); segments.addAll(i, steps); int addedSegments = steps.size() - 1; @@ -66,7 +68,7 @@ final class RampToStepAdapter implements VibrationSegmentsAdapter { return repeatIndex; } - private List apply(VibratorInfo info, RampSegment ramp) { + private List convertRampToSteps(VibratorInfo info, RampSegment ramp) { if (Float.compare(ramp.getStartAmplitude(), ramp.getEndAmplitude()) == 0) { // Amplitude is the same, so return a single step to simulate this ramp. return Arrays.asList( diff --git a/services/core/java/com/android/server/vibrator/SplitSegmentsAdapter.java b/services/core/java/com/android/server/vibrator/SplitSegmentsAdapter.java new file mode 100644 index 0000000000000..347db35442148 --- /dev/null +++ b/services/core/java/com/android/server/vibrator/SplitSegmentsAdapter.java @@ -0,0 +1,115 @@ +/* + * Copyright (C) 2023 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 com.android.server.vibrator; + +import android.hardware.vibrator.IVibrator; +import android.os.VibratorInfo; +import android.os.vibrator.RampSegment; +import android.os.vibrator.VibrationEffectSegment; +import android.util.MathUtils; + +import java.util.ArrayList; +import java.util.List; + +/** + * Adapter that splits segments with longer duration than the device capabilities. + * + *

This transformation replaces large {@link RampSegment} entries by a sequence of smaller + * ramp segments that starts and ends at the same amplitudes/frequencies, interpolating the + * intermediate values. + * + *

The segments will not be changed if the device doesn't have + * {@link IVibrator#CAP_COMPOSE_PWLE_EFFECTS}. + */ +final class SplitSegmentsAdapter implements VibrationSegmentsAdapter { + + @Override + public int adaptToVibrator(VibratorInfo info, List segments, + int repeatIndex) { + if (!info.hasCapability(IVibrator.CAP_COMPOSE_PWLE_EFFECTS)) { + // The vibrator does not have PWLE capability, so keep the segments unchanged. + return repeatIndex; + } + int maxRampDuration = info.getPwlePrimitiveDurationMax(); + if (maxRampDuration <= 0) { + // No limit set to PWLE primitive duration. + return repeatIndex; + } + + int segmentCount = segments.size(); + for (int i = 0; i < segmentCount; i++) { + if (!(segments.get(i) instanceof RampSegment)) { + continue; + } + RampSegment ramp = (RampSegment) segments.get(i); + int splits = ((int) ramp.getDuration() + maxRampDuration - 1) / maxRampDuration; + if (splits <= 1) { + continue; + } + segments.remove(i); + segments.addAll(i, splitRampSegment(info, ramp, splits)); + int addedSegments = splits - 1; + if (repeatIndex > i) { + repeatIndex += addedSegments; + } + i += addedSegments; + segmentCount += addedSegments; + } + + return repeatIndex; + } + + private static List splitRampSegment(VibratorInfo info, RampSegment ramp, + int splits) { + List ramps = new ArrayList<>(splits); + // Fill zero frequency values with the device resonant frequency before interpolating. + float startFrequencyHz = fillEmptyFrequency(info, ramp.getStartFrequencyHz()); + float endFrequencyHz = fillEmptyFrequency(info, ramp.getEndFrequencyHz()); + long splitDuration = ramp.getDuration() / splits; + float previousAmplitude = ramp.getStartAmplitude(); + float previousFrequencyHz = startFrequencyHz; + long accumulatedDuration = 0; + + for (int i = 1; i < splits; i++) { + accumulatedDuration += splitDuration; + float durationRatio = (float) accumulatedDuration / ramp.getDuration(); + float interpolatedFrequency = + MathUtils.lerp(startFrequencyHz, endFrequencyHz, durationRatio); + float interpolatedAmplitude = + MathUtils.lerp(ramp.getStartAmplitude(), ramp.getEndAmplitude(), durationRatio); + RampSegment rampSplit = new RampSegment( + previousAmplitude, interpolatedAmplitude, + previousFrequencyHz, interpolatedFrequency, + (int) splitDuration); + ramps.add(rampSplit); + previousAmplitude = rampSplit.getEndAmplitude(); + previousFrequencyHz = rampSplit.getEndFrequencyHz(); + } + + ramps.add(new RampSegment(previousAmplitude, ramp.getEndAmplitude(), previousFrequencyHz, + endFrequencyHz, (int) (ramp.getDuration() - accumulatedDuration))); + + return ramps; + } + + private static float fillEmptyFrequency(VibratorInfo info, float frequencyHz) { + if (Float.isNaN(info.getResonantFrequencyHz())) { + return frequencyHz; + } + return frequencyHz == 0 ? info.getResonantFrequencyHz() : frequencyHz; + } +} diff --git a/services/core/java/com/android/server/vibrator/StepToRampAdapter.java b/services/core/java/com/android/server/vibrator/StepToRampAdapter.java index d86ee7858c572..6616f10cf304c 100644 --- a/services/core/java/com/android/server/vibrator/StepToRampAdapter.java +++ b/services/core/java/com/android/server/vibrator/StepToRampAdapter.java @@ -21,17 +21,17 @@ import android.os.VibratorInfo; import android.os.vibrator.RampSegment; import android.os.vibrator.StepSegment; import android.os.vibrator.VibrationEffectSegment; -import android.util.MathUtils; -import java.util.ArrayList; import java.util.List; /** * Adapter that converts step segments that should be handled as PWLEs to ramp segments. * - *

Each replaced {@link StepSegment} will be represented by a {@link RampSegment} with same - * start and end amplitudes/frequencies, which can then be converted to PWLE compositions. This - * adapter leaves the segments unchanged if the device doesn't have the PWLE composition capability. + *

Each replaced step will be represented by a ramp with same start and end + * amplitudes/frequencies, which can then be converted to PWLE compositions. + * + *

The segments will not be changed if the device doesn't have + * {@link IVibrator#CAP_COMPOSE_PWLE_EFFECTS}. */ final class StepToRampAdapter implements VibrationSegmentsAdapter { @@ -42,12 +42,6 @@ final class StepToRampAdapter implements VibrationSegmentsAdapter { // The vibrator does not have PWLE capability, so keep the segments unchanged. return repeatIndex; } - convertStepsToRamps(info, segments); - repeatIndex = splitLongRampSegments(info, segments, repeatIndex); - return repeatIndex; - } - - private void convertStepsToRamps(VibratorInfo info, List segments) { int segmentCount = segments.size(); // Convert steps that require frequency control to ramps. for (int i = 0; i < segmentCount; i++) { @@ -68,40 +62,6 @@ final class StepToRampAdapter implements VibrationSegmentsAdapter { } } } - } - - /** - * Split {@link RampSegment} entries that have duration longer than {@link - * VibratorInfo#getPwlePrimitiveDurationMax()}. - */ - private int splitLongRampSegments(VibratorInfo info, List segments, - int repeatIndex) { - int maxDuration = info.getPwlePrimitiveDurationMax(); - if (maxDuration <= 0) { - // No limit set to PWLE primitive duration. - return repeatIndex; - } - - int segmentCount = segments.size(); - for (int i = 0; i < segmentCount; i++) { - if (!(segments.get(i) instanceof RampSegment)) { - continue; - } - RampSegment ramp = (RampSegment) segments.get(i); - int splits = ((int) ramp.getDuration() + maxDuration - 1) / maxDuration; - if (splits <= 1) { - continue; - } - segments.remove(i); - segments.addAll(i, splitRampSegment(info, ramp, splits)); - int addedSegments = splits - 1; - if (repeatIndex > i) { - repeatIndex += addedSegments; - } - i += addedSegments; - segmentCount += addedSegments; - } - return repeatIndex; } @@ -111,38 +71,6 @@ final class StepToRampAdapter implements VibrationSegmentsAdapter { frequencyHz, frequencyHz, (int) segment.getDuration()); } - private static List splitRampSegment(VibratorInfo info, RampSegment ramp, - int splits) { - List ramps = new ArrayList<>(splits); - float startFrequencyHz = fillEmptyFrequency(info, ramp.getStartFrequencyHz()); - float endFrequencyHz = fillEmptyFrequency(info, ramp.getEndFrequencyHz()); - long splitDuration = ramp.getDuration() / splits; - float previousAmplitude = ramp.getStartAmplitude(); - float previousFrequency = startFrequencyHz; - long accumulatedDuration = 0; - - for (int i = 1; i < splits; i++) { - accumulatedDuration += splitDuration; - float durationRatio = (float) accumulatedDuration / ramp.getDuration(); - float interpolatedFrequency = - MathUtils.lerp(startFrequencyHz, endFrequencyHz, durationRatio); - float interpolatedAmplitude = - MathUtils.lerp(ramp.getStartAmplitude(), ramp.getEndAmplitude(), durationRatio); - RampSegment rampSplit = new RampSegment( - previousAmplitude, interpolatedAmplitude, - previousFrequency, interpolatedFrequency, - (int) splitDuration); - ramps.add(rampSplit); - previousAmplitude = rampSplit.getEndAmplitude(); - previousFrequency = rampSplit.getEndFrequencyHz(); - } - - ramps.add(new RampSegment(previousAmplitude, ramp.getEndAmplitude(), previousFrequency, - endFrequencyHz, (int) (ramp.getDuration() - accumulatedDuration))); - - return ramps; - } - private static boolean isStep(VibrationEffectSegment segment) { return segment instanceof StepSegment; } diff --git a/services/tests/vibrator/src/com/android/server/vibrator/RampToStepAdapterTest.java b/services/tests/vibrator/src/com/android/server/vibrator/RampToStepAdapterTest.java index 8bb21b3af4485..867c061151b47 100644 --- a/services/tests/vibrator/src/com/android/server/vibrator/RampToStepAdapterTest.java +++ b/services/tests/vibrator/src/com/android/server/vibrator/RampToStepAdapterTest.java @@ -66,6 +66,9 @@ public class RampToStepAdapterTest { assertEquals(-1, mAdapter.adaptToVibrator(EMPTY_VIBRATOR_INFO, segments, -1)); assertEquals(1, mAdapter.adaptToVibrator(EMPTY_VIBRATOR_INFO, segments, 1)); + assertEquals(-1, mAdapter.adaptToVibrator(PWLE_VIBRATOR_INFO, segments, -1)); + assertEquals(1, mAdapter.adaptToVibrator(PWLE_VIBRATOR_INFO, segments, 1)); + assertEquals(originalSegments, segments); } diff --git a/services/tests/vibrator/src/com/android/server/vibrator/SplitSegmentsAdapterTest.java b/services/tests/vibrator/src/com/android/server/vibrator/SplitSegmentsAdapterTest.java new file mode 100644 index 0000000000000..6630ccad61894 --- /dev/null +++ b/services/tests/vibrator/src/com/android/server/vibrator/SplitSegmentsAdapterTest.java @@ -0,0 +1,144 @@ +/* + * Copyright (C) 2023 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 com.android.server.vibrator; + +import static com.google.common.truth.Truth.assertThat; + +import android.hardware.vibrator.IVibrator; +import android.os.VibrationEffect; +import android.os.VibratorInfo; +import android.os.vibrator.PrebakedSegment; +import android.os.vibrator.PrimitiveSegment; +import android.os.vibrator.RampSegment; +import android.os.vibrator.StepSegment; +import android.os.vibrator.VibrationEffectSegment; + +import org.junit.Before; +import org.junit.Test; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.stream.IntStream; + +public class SplitSegmentsAdapterTest { + private static final int PWLE_COMPOSITION_PRIMITIVE_DURATION_MAX = 10; + + private static final float[] TEST_AMPLITUDE_MAP = new float[]{ + /* 50Hz= */ 0.1f, 0.2f, 0.4f, 0.8f, /* 150Hz= */ 1f, 0.9f, /* 200Hz= */ 0.8f}; + + private static final VibratorInfo.FrequencyProfile TEST_FREQUENCY_PROFILE = + new VibratorInfo.FrequencyProfile( + /* resonantFrequencyHz= */ 150f, /* minFrequencyHz= */ 50f, + /* frequencyResolutionHz= */ 25f, TEST_AMPLITUDE_MAP); + + private static final VibratorInfo EMPTY_VIBRATOR_INFO = createVibratorInfo(); + private static final VibratorInfo PWLE_VIBRATOR_INFO = createVibratorInfo( + IVibrator.CAP_COMPOSE_PWLE_EFFECTS); + + private SplitSegmentsAdapter mAdapter; + + @Before + public void setUp() throws Exception { + mAdapter = new SplitSegmentsAdapter(); + } + + @Test + public void testStepAndPrebakedAndPrimitiveSegments_returnsOriginalSegments() { + List segments = new ArrayList<>(Arrays.asList( + new StepSegment(/* amplitude= */ 1, /* frequencyHz= */ 40f, /* duration= */ 100), + new PrebakedSegment( + VibrationEffect.EFFECT_CLICK, false, VibrationEffect.EFFECT_STRENGTH_LIGHT), + new PrimitiveSegment(VibrationEffect.Composition.PRIMITIVE_TICK, 1, 10))); + List originalSegments = new ArrayList<>(segments); + + assertThat(mAdapter.adaptToVibrator(EMPTY_VIBRATOR_INFO, segments, /*repeatIndex= */ -1)) + .isEqualTo(-1); + assertThat(mAdapter.adaptToVibrator(EMPTY_VIBRATOR_INFO, segments, /*repeatIndex= */ 1)) + .isEqualTo(1); + + assertThat(mAdapter.adaptToVibrator(PWLE_VIBRATOR_INFO, segments, /*repeatIndex= */ -1)) + .isEqualTo(-1); + assertThat(mAdapter.adaptToVibrator(PWLE_VIBRATOR_INFO, segments, /*repeatIndex= */ 1)) + .isEqualTo(1); + + assertThat(segments).isEqualTo(originalSegments); + } + + @Test + public void testRampSegments_noPwleCapabilities_returnsOriginalSegments() { + List segments = new ArrayList<>(Arrays.asList( + new RampSegment(/* startAmplitude= */ 0.5f, /* endAmplitude*/ 0.5f, + /* startFrequencyHz= */ 10, /* endFrequencyHz= */ 10, /* duration= */ 10), + new RampSegment(/* startAmplitude= */ 0.2f, /* endAmplitude*/ 0.8f, + /* startFrequencyHz= */ 60, /* endFrequencyHz= */ 90, /* duration= */ 10))); + List originalSegments = new ArrayList<>(segments); + + assertThat(mAdapter.adaptToVibrator(EMPTY_VIBRATOR_INFO, segments, /*repeatIndex= */ -1)) + .isEqualTo(-1); + assertThat(mAdapter.adaptToVibrator(EMPTY_VIBRATOR_INFO, segments, /*repeatIndex= */ 1)) + .isEqualTo(1); + + assertThat(segments).isEqualTo(originalSegments); + } + + @Test + public void testRampSegments_withPwleDurationLimit_splitsLongRampsAndPreserveOtherSegments() { + List segments = new ArrayList<>(Arrays.asList( + new StepSegment(/* amplitude= */ 1, /* frequencyHz= */ 40f, /* duration= */ 100), + new RampSegment(/* startAmplitude= */ 0.5f, /* endAmplitude*/ 0.5f, + /* startFrequencyHz= */ 10, /* endFrequencyHz= */ 10, /* duration= */ 10), + new RampSegment(/* startAmplitude= */ 0, /* endAmplitude= */ 1, + /* startFrequencyHz= */ 0, /* endFrequencyHz= */ 50, /* duration= */ 25), + new StepSegment(/* amplitude= */ 1, /* frequencyHz= */ 40f, /* duration= */ 100), + new RampSegment(/* startAmplitude= */ 1, /* endAmplitude*/ 1, + /* startFrequencyHz= */ 10, /* endFrequencyHz= */ 20, /* duration= */ 5))); + List expectedSegments = Arrays.asList( + new StepSegment(/* amplitude= */ 1, /* frequencyHz= */ 40f, /* duration= */ 100), + new RampSegment(/* startAmplitude= */ 0.5f, /* endAmplitude*/ 0.5f, + /* startFrequencyHz= */ 10, /* endFrequencyHz= */ 10, /* duration= */ 10), + new RampSegment(/* startAmplitude= */ 0, /* endAmplitude= */ 0.32f, + /* startFrequencyHz= */ 150, /* endFrequencyHz= */ 118f, /* duration= */ 8), + new RampSegment(/* startAmplitude= */ 0.32f, /* endAmplitude= */ 0.64f, + /* startFrequencyHz= */ 118f, /* endFrequencyHz= */ 86f, + /* duration= */ 8), + new RampSegment(/* startAmplitude= */ 0.64f, /* endAmplitude= */ 1, + /* startFrequencyHz= */ 86f, /* endFrequencyHz= */ 50f, /* duration= */ 9), + new StepSegment(/* amplitude= */ 1, /* frequencyHz= */ 40f, /* duration= */ 100), + new RampSegment(/* startAmplitude= */ 1, /* endAmplitude*/ 1, + /* startFrequencyHz= */ 10, /* endFrequencyHz= */ 20, /* duration= */ 5)); + + VibratorInfo vibratorInfo = new VibratorInfo.Builder(0) + .setCapabilities(IVibrator.CAP_COMPOSE_PWLE_EFFECTS) + .setPwlePrimitiveDurationMax(10) + .setFrequencyProfile(TEST_FREQUENCY_PROFILE) + .build(); + + // Update repeat index to skip the ramp splits. + assertThat(mAdapter.adaptToVibrator(vibratorInfo, segments, /*repeatIndex= */ 3)) + .isEqualTo(5); + assertThat(segments).isEqualTo(expectedSegments); + } + + private static VibratorInfo createVibratorInfo(int... capabilities) { + return new VibratorInfo.Builder(0) + .setCapabilities(IntStream.of(capabilities).reduce((a, b) -> a | b).orElse(0)) + .setFrequencyProfile(TEST_FREQUENCY_PROFILE) + .setPwlePrimitiveDurationMax(PWLE_COMPOSITION_PRIMITIVE_DURATION_MAX) + .build(); + } +} diff --git a/services/tests/vibrator/src/com/android/server/vibrator/StepToRampAdapterTest.java b/services/tests/vibrator/src/com/android/server/vibrator/StepToRampAdapterTest.java index 58deeecd35c9c..82deff011e82b 100644 --- a/services/tests/vibrator/src/com/android/server/vibrator/StepToRampAdapterTest.java +++ b/services/tests/vibrator/src/com/android/server/vibrator/StepToRampAdapterTest.java @@ -66,42 +66,12 @@ public class StepToRampAdapterTest { assertEquals(-1, mAdapter.adaptToVibrator(EMPTY_VIBRATOR_INFO, segments, -1)); assertEquals(1, mAdapter.adaptToVibrator(EMPTY_VIBRATOR_INFO, segments, 1)); + assertEquals(-1, mAdapter.adaptToVibrator(PWLE_VIBRATOR_INFO, segments, -1)); + assertEquals(1, mAdapter.adaptToVibrator(PWLE_VIBRATOR_INFO, segments, 1)); + assertEquals(originalSegments, segments); } - @Test - public void testRampSegments_withPwleDurationLimit_splitsLongRamps() { - List segments = new ArrayList<>(Arrays.asList( - new RampSegment(/* startAmplitude= */ 0.5f, /* endAmplitude*/ 0.5f, - /* startFrequencyHz= */ 10, /* endFrequencyHz= */ 10, /* duration= */ 10), - new RampSegment(/* startAmplitude= */ 0, /* endAmplitude= */ 1, - /* startFrequencyHz= */ 0, /* endFrequencyHz= */ 50, /* duration= */ 25), - new RampSegment(/* startAmplitude= */ 1, /* endAmplitude*/ 1, - /* startFrequencyHz= */ 10, /* endFrequencyHz= */ 20, /* duration= */ 5))); - List expectedSegments = Arrays.asList( - new RampSegment(/* startAmplitude= */ 0.5f, /* endAmplitude*/ 0.5f, - /* startFrequencyHz= */ 10, /* endFrequencyHz= */ 10, /* duration= */ 10), - new RampSegment(/* startAmplitude= */ 0, /* endAmplitude= */ 0.32f, - /* startFrequencyHz= */ 150, /* endFrequencyHz= */ 118f, /* duration= */ 8), - new RampSegment(/* startAmplitude= */ 0.32f, /* endAmplitude= */ 0.64f, - /* startFrequencyHz= */ 118f, /* endFrequencyHz= */ 86f, - /* duration= */ 8), - new RampSegment(/* startAmplitude= */ 0.64f, /* endAmplitude= */ 1, - /* startFrequencyHz= */ 86f, /* endFrequencyHz= */ 50f, /* duration= */ 9), - new RampSegment(/* startAmplitude= */ 1, /* endAmplitude*/ 1, - /* startFrequencyHz= */ 10, /* endFrequencyHz= */ 20, /* duration= */ 5)); - - VibratorInfo vibratorInfo = new VibratorInfo.Builder(0) - .setCapabilities(IVibrator.CAP_COMPOSE_PWLE_EFFECTS) - .setPwlePrimitiveDurationMax(10) - .setFrequencyProfile(TEST_FREQUENCY_PROFILE) - .build(); - - // Update repeat index to skip the ramp splits. - assertEquals(4, mAdapter.adaptToVibrator(vibratorInfo, segments, 2)); - assertEquals(expectedSegments, segments); - } - @Test public void testStepAndRampSegments_withoutPwleCapability_keepsListUnchanged() { List segments = new ArrayList<>(Arrays.asList(