Loading core/api/current.txt +1 −0 Original line number Diff line number Diff line Loading @@ -34531,6 +34531,7 @@ package android.os { method public int describeContents(); method @NonNull public static android.os.VibrationEffect.Composition startComposition(); method @FlaggedApi("android.os.vibrator.normalized_pwle_effects") @NonNull public static android.os.VibrationEffect.WaveformEnvelopeBuilder startWaveformEnvelope(); method @FlaggedApi("android.os.vibrator.normalized_pwle_effects") @NonNull public static android.os.VibrationEffect.WaveformEnvelopeBuilder startWaveformEnvelope(@FloatRange(from=0) float); field @NonNull public static final android.os.Parcelable.Creator<android.os.VibrationEffect> CREATOR; field public static final int DEFAULT_AMPLITUDE = -1; // 0xffffffff field public static final int EFFECT_CLICK = 0; // 0x0 core/java/android/os/VibrationEffect.java +34 −2 Original line number Diff line number Diff line Loading @@ -1740,7 +1740,9 @@ public abstract class VibrationEffect implements Parcelable { * * <p>The waveform envelope builder offers more flexibility for creating waveform effects, * allowing control over vibration amplitude and frequency via smooth transitions between * values. * values. The waveform will start the first transition from the vibrator off state, using * the same frequency of the first control point. To provide a different initial vibration * frequency, use {@link #startWaveformEnvelope(float)}. * * <p>Note: To check whether waveform envelope effects are supported, use * {@link Vibrator#areEnvelopeEffectsSupported()}. Loading @@ -1753,6 +1755,32 @@ public abstract class VibrationEffect implements Parcelable { return new WaveformEnvelopeBuilder(); } /** * Start building a waveform vibration with an initial frequency. * * <p>The waveform envelope builder offers more flexibility for creating waveform effects, * allowing control over vibration amplitude and frequency via smooth transitions between * values. * * <p>This is the same as {@link #startWaveformEnvelope()}, but the waveform will start * vibrating at given frequency, in hertz, while it transitions to the new amplitude and * frequency of the first control point. * * <p>Note: To check whether waveform envelope effects are supported, use * {@link Vibrator#areEnvelopeEffectsSupported()}. * * @param initialFrequencyHz The starting frequency of the vibration, in hertz. Must be greater * than zero. * * @see VibrationEffect.WaveformEnvelopeBuilder */ @FlaggedApi(Flags.FLAG_NORMALIZED_PWLE_EFFECTS) @NonNull public static VibrationEffect.WaveformEnvelopeBuilder startWaveformEnvelope( @FloatRange(from = 0) float initialFrequencyHz) { return new WaveformEnvelopeBuilder(initialFrequencyHz); } /** * A builder for waveform effects described by its envelope. * Loading Loading @@ -1810,6 +1838,10 @@ public abstract class VibrationEffect implements Parcelable { private WaveformEnvelopeBuilder() {} private WaveformEnvelopeBuilder(float initialFrequency) { mLastFrequencyHz = initialFrequency; } /** * Adds a new control point to the end of this waveform envelope. * Loading Loading @@ -1841,7 +1873,7 @@ public abstract class VibrationEffect implements Parcelable { @FloatRange(from = 0, to = 1) float amplitude, @FloatRange(from = 0) float frequencyHz, int timeMillis) { if (mSegments.isEmpty()) { if (mLastFrequencyHz == 0) { mLastFrequencyHz = frequencyHz; } Loading core/java/android/os/vibrator/PwlePoint.java 0 → 100644 +66 −0 Original line number Diff line number Diff line /* * Copyright (C) 2024 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 android.os.vibrator; import java.util.Objects; /** * A {@link PwlePoint} represents a single point in an envelope vibration effect. Defined by its * amplitude, frequency and time to transition to this point from the previous one in the envelope. * * @hide */ public final class PwlePoint { private final float mAmplitude; private final float mFrequencyHz; private final int mTimeMillis; /** @hide */ public PwlePoint(float amplitude, float frequencyHz, int timeMillis) { mAmplitude = amplitude; mFrequencyHz = frequencyHz; mTimeMillis = timeMillis; } public float getAmplitude() { return mAmplitude; } public float getFrequencyHz() { return mFrequencyHz; } public int getTimeMillis() { return mTimeMillis; } @Override public boolean equals(Object obj) { if (!(obj instanceof PwlePoint)) { return false; } PwlePoint other = (PwlePoint) obj; return Float.compare(mAmplitude, other.mAmplitude) == 0 && Float.compare(mFrequencyHz, other.mFrequencyHz) == 0 && mTimeMillis == other.mTimeMillis; } @Override public int hashCode() { return Objects.hash(mAmplitude, mFrequencyHz, mTimeMillis); } } core/tests/vibrator/src/android/os/VibrationEffectTest.java +68 −0 Original line number Diff line number Diff line Loading @@ -425,6 +425,15 @@ public class VibrationEffectTest { .build(); assertNull(effect.computeCreateWaveformOffOnTimingsOrNull()); effect = VibrationEffect.startWaveformEnvelope(/*initialFrequency=*/ 60) .addControlPoint(/*amplitude=*/ 0.0f, /*frequencyHz=*/ 60f, /*timeMillis=*/ 20) .addControlPoint(/*amplitude=*/ 0.3f, /*frequencyHz=*/ 100f, /*timeMillis=*/ 50) .addControlPoint(/*amplitude=*/ 0.4f, /*frequencyHz=*/ 120f, /*timeMillis=*/ 80) .addControlPoint(/*amplitude=*/ 0.0f, /*frequencyHz=*/ 120f, /*timeMillis=*/ 40) .build(); assertNull(effect.computeCreateWaveformOffOnTimingsOrNull()); } @Test Loading Loading @@ -643,6 +652,14 @@ public class VibrationEffectTest { .build() .validate(); VibrationEffect.startWaveformEnvelope(/*initialFrequency=*/ 30) .addControlPoint(/*amplitude=*/ 0.0f, /*frequencyHz=*/ 60f, /*timeMillis=*/ 20) .addControlPoint(/*amplitude=*/ 0.3f, /*frequencyHz=*/ 100f, /*timeMillis=*/ 50) .addControlPoint(/*amplitude=*/ 0.4f, /*frequencyHz=*/ 120f, /*timeMillis=*/ 80) .addControlPoint(/*amplitude=*/ 0.0f, /*frequencyHz=*/ 120f, /*timeMillis=*/ 40) .build() .validate(); VibrationEffect.createRepeatingEffect( /*preamble=*/ VibrationEffect.startWaveformEnvelope() .addControlPoint(/*amplitude=*/ 0.0f, /*frequencyHz=*/ 60f, Loading Loading @@ -693,6 +710,34 @@ public class VibrationEffectTest { /*timeMillis=*/ 0) .build() .validate()); assertThrows(IllegalStateException.class, () -> VibrationEffect.startWaveformEnvelope(/*initialFrequency=*/30) .build().validate()); assertThrows(IllegalArgumentException.class, () -> VibrationEffect.startWaveformEnvelope(/*initialFrequency=*/30) .addControlPoint(/*amplitude=*/ -1.0f, /*frequencyHz=*/ 60f, /*timeMillis=*/ 20) .build() .validate()); assertThrows(IllegalArgumentException.class, () -> VibrationEffect.startWaveformEnvelope(/*initialFrequency=*/30) .addControlPoint(/*amplitude=*/ 1.1f, /*frequencyHz=*/ 60f, /*timeMillis=*/ 20) .build() .validate()); assertThrows(IllegalArgumentException.class, () -> VibrationEffect.startWaveformEnvelope(/*initialFrequency=*/30) .addControlPoint(/*amplitude=*/ 0.8f, /*frequencyHz=*/ 0f, /*timeMillis=*/ 20) .build() .validate()); assertThrows(IllegalArgumentException.class, () -> VibrationEffect.startWaveformEnvelope(/*initialFrequency=*/30) .addControlPoint(/*amplitude=*/ 0.8f, /*frequencyHz=*/ 100f, /*timeMillis=*/ 0) .build() .validate()); } @Test Loading Loading @@ -1331,6 +1376,11 @@ public class VibrationEffectTest { .addTransition(Duration.ofMillis(500), targetAmplitude(0)) .build() .isHapticFeedbackCandidate()); } @Test @EnableFlags(Flags.FLAG_NORMALIZED_PWLE_EFFECTS) public void testIsHapticFeedbackCandidate_longEnvelopeEffects_notCandidates() { assertFalse(VibrationEffect.startWaveformEnvelope() .addControlPoint(/*amplitude=*/ 0.0f, /*frequencyHz=*/ 60f, /*timeMillis=*/ 200) .addControlPoint(/*amplitude=*/ 0.3f, /*frequencyHz=*/ 100f, /*timeMillis=*/ 500) Loading @@ -1338,6 +1388,13 @@ public class VibrationEffectTest { .addControlPoint(/*amplitude=*/ 0.0f, /*frequencyHz=*/ 120f, /*timeMillis=*/ 400) .build() .isHapticFeedbackCandidate()); assertFalse(VibrationEffect.startWaveformEnvelope(/*initialFrequency=*/ 40) .addControlPoint(/*amplitude=*/ 0.0f, /*frequencyHz=*/ 60f, /*timeMillis=*/ 200) .addControlPoint(/*amplitude=*/ 0.3f, /*frequencyHz=*/ 100f, /*timeMillis=*/ 500) .addControlPoint(/*amplitude=*/ 0.4f, /*frequencyHz=*/ 120f, /*timeMillis=*/ 800) .addControlPoint(/*amplitude=*/ 0.0f, /*frequencyHz=*/ 120f, /*timeMillis=*/ 400) .build() .isHapticFeedbackCandidate()); } @Test Loading @@ -1351,12 +1408,23 @@ public class VibrationEffectTest { .addTransition(Duration.ofMillis(300), targetAmplitude(0)) .build() .isHapticFeedbackCandidate()); } @Test @EnableFlags(Flags.FLAG_NORMALIZED_PWLE_EFFECTS) public void testIsHapticFeedbackCandidate_shortEnvelopeEffects_areCandidates() { assertTrue(VibrationEffect.startWaveformEnvelope() .addControlPoint(/*amplitude=*/ 0.3f, /*frequencyHz=*/ 100f, /*timeMillis=*/ 500) .addControlPoint(/*amplitude=*/ 0.4f, /*frequencyHz=*/ 120f, /*timeMillis=*/ 400) .addControlPoint(/*amplitude=*/ 0.0f, /*frequencyHz=*/ 120f, /*timeMillis=*/ 100) .build() .isHapticFeedbackCandidate()); assertTrue(VibrationEffect.startWaveformEnvelope(/*initialFrequency=*/ 30) .addControlPoint(/*amplitude=*/ 0.3f, /*frequencyHz=*/ 100f, /*timeMillis=*/ 500) .addControlPoint(/*amplitude=*/ 0.4f, /*frequencyHz=*/ 120f, /*timeMillis=*/ 400) .addControlPoint(/*amplitude=*/ 0.0f, /*frequencyHz=*/ 120f, /*timeMillis=*/ 100) .build() .isHapticFeedbackCandidate()); } @Test Loading services/core/java/com/android/server/vibrator/ComposePwleV2VibratorStep.java +23 −14 Original line number Diff line number Diff line Loading @@ -20,6 +20,7 @@ import android.annotation.NonNull; import android.os.Trace; import android.os.VibrationEffect; import android.os.vibrator.Flags; import android.os.vibrator.PwlePoint; import android.os.vibrator.PwleSegment; import android.os.vibrator.VibrationEffectSegment; import android.util.Slog; Loading Loading @@ -57,7 +58,7 @@ final class ComposePwleV2VibratorStep extends AbstractComposedVibratorStep { // Load the next PwleSegments to create a single composePwleV2 call to the vibrator, // limited to the vibrator's maximum envelope effect size. int limit = controller.getVibratorInfo().getMaxEnvelopeEffectSize(); List<PwleSegment> pwles = unrollPwleSegments(effect, segmentIndex, limit); List<PwlePoint> pwles = unrollPwleSegments(effect, segmentIndex, limit); if (pwles.isEmpty()) { Slog.w(VibrationThread.TAG, "Ignoring wrong segment for a ComposeEnvelopeStep: " Loading @@ -70,7 +71,7 @@ final class ComposePwleV2VibratorStep extends AbstractComposedVibratorStep { Slog.d(VibrationThread.TAG, "Compose " + pwles + " PWLEs on vibrator " + controller.getVibratorInfo().getId()); } PwleSegment[] pwlesArray = pwles.toArray(new PwleSegment[pwles.size()]); PwlePoint[] pwlesArray = pwles.toArray(new PwlePoint[pwles.size()]); long vibratorOnResult = controller.on(pwlesArray, getVibration().id); handleVibratorOnResult(vibratorOnResult); getVibration().stats.reportComposePwle(vibratorOnResult, pwlesArray); Loading @@ -82,9 +83,9 @@ final class ComposePwleV2VibratorStep extends AbstractComposedVibratorStep { } } private List<PwleSegment> unrollPwleSegments(VibrationEffect.Composed effect, int startIndex, private List<PwlePoint> unrollPwleSegments(VibrationEffect.Composed effect, int startIndex, int limit) { List<PwleSegment> segments = new ArrayList<>(limit); List<PwlePoint> pwlePoints = new ArrayList<>(limit); float bestBreakAmplitude = 1; int bestBreakPosition = limit; // Exclusive index. Loading @@ -93,7 +94,7 @@ final class ComposePwleV2VibratorStep extends AbstractComposedVibratorStep { // Loop once after reaching the limit to see if breaking it will really be necessary, then // apply the best break position found, otherwise return the full list as it fits the limit. for (int i = startIndex; segments.size() <= limit; i++) { for (int i = startIndex; pwlePoints.size() < limit; i++) { if (i == segmentCount) { if (repeatIndex >= 0) { i = repeatIndex; Loading @@ -104,12 +105,20 @@ final class ComposePwleV2VibratorStep extends AbstractComposedVibratorStep { } VibrationEffectSegment segment = effect.getSegments().get(i); if (segment instanceof PwleSegment pwleSegment) { segments.add(pwleSegment); if (pwlePoints.isEmpty()) { // The initial state is defined by the starting amplitude and frequency of the // first PwleSegment. The time parameter is set to zero to indicate this is // the initial condition without any ramp up time. pwlePoints.add(new PwlePoint(pwleSegment.getStartAmplitude(), pwleSegment.getStartFrequencyHz(), /*timeMillis=*/ 0)); } pwlePoints.add(new PwlePoint(pwleSegment.getEndAmplitude(), pwleSegment.getEndFrequencyHz(), (int) pwleSegment.getDuration())); if (isBetterBreakPosition(segments, bestBreakAmplitude, limit)) { if (isBetterBreakPosition(pwlePoints, bestBreakAmplitude, limit)) { // Mark this position as the best one so far to break a long waveform. bestBreakAmplitude = pwleSegment.getEndAmplitude(); bestBreakPosition = segments.size(); // Break after this pwle ends. bestBreakPosition = pwlePoints.size(); // Break after this pwle ends. } } else { // First non-pwle segment, stop collecting pwles. Loading @@ -117,21 +126,21 @@ final class ComposePwleV2VibratorStep extends AbstractComposedVibratorStep { } } return segments.size() > limit return pwlePoints.size() > limit // Remove excessive segments, using the best breaking position recorded. ? segments.subList(0, bestBreakPosition) ? pwlePoints.subList(0, bestBreakPosition) // Return all collected pwle segments. : segments; : pwlePoints; } /** * Returns true if the current segment list represents a better break position for a PWLE, * given the current amplitude being used for breaking it at a smaller size and the size limit. */ private boolean isBetterBreakPosition(List<PwleSegment> segments, private boolean isBetterBreakPosition(List<PwlePoint> segments, float currentBestBreakAmplitude, int limit) { PwleSegment lastSegment = segments.get(segments.size() - 1); float breakAmplitudeCandidate = lastSegment.getEndAmplitude(); PwlePoint lastSegment = segments.get(segments.size() - 1); float breakAmplitudeCandidate = lastSegment.getAmplitude(); int breakPositionCandidate = segments.size(); if (breakPositionCandidate > limit) { Loading Loading
core/api/current.txt +1 −0 Original line number Diff line number Diff line Loading @@ -34531,6 +34531,7 @@ package android.os { method public int describeContents(); method @NonNull public static android.os.VibrationEffect.Composition startComposition(); method @FlaggedApi("android.os.vibrator.normalized_pwle_effects") @NonNull public static android.os.VibrationEffect.WaveformEnvelopeBuilder startWaveformEnvelope(); method @FlaggedApi("android.os.vibrator.normalized_pwle_effects") @NonNull public static android.os.VibrationEffect.WaveformEnvelopeBuilder startWaveformEnvelope(@FloatRange(from=0) float); field @NonNull public static final android.os.Parcelable.Creator<android.os.VibrationEffect> CREATOR; field public static final int DEFAULT_AMPLITUDE = -1; // 0xffffffff field public static final int EFFECT_CLICK = 0; // 0x0
core/java/android/os/VibrationEffect.java +34 −2 Original line number Diff line number Diff line Loading @@ -1740,7 +1740,9 @@ public abstract class VibrationEffect implements Parcelable { * * <p>The waveform envelope builder offers more flexibility for creating waveform effects, * allowing control over vibration amplitude and frequency via smooth transitions between * values. * values. The waveform will start the first transition from the vibrator off state, using * the same frequency of the first control point. To provide a different initial vibration * frequency, use {@link #startWaveformEnvelope(float)}. * * <p>Note: To check whether waveform envelope effects are supported, use * {@link Vibrator#areEnvelopeEffectsSupported()}. Loading @@ -1753,6 +1755,32 @@ public abstract class VibrationEffect implements Parcelable { return new WaveformEnvelopeBuilder(); } /** * Start building a waveform vibration with an initial frequency. * * <p>The waveform envelope builder offers more flexibility for creating waveform effects, * allowing control over vibration amplitude and frequency via smooth transitions between * values. * * <p>This is the same as {@link #startWaveformEnvelope()}, but the waveform will start * vibrating at given frequency, in hertz, while it transitions to the new amplitude and * frequency of the first control point. * * <p>Note: To check whether waveform envelope effects are supported, use * {@link Vibrator#areEnvelopeEffectsSupported()}. * * @param initialFrequencyHz The starting frequency of the vibration, in hertz. Must be greater * than zero. * * @see VibrationEffect.WaveformEnvelopeBuilder */ @FlaggedApi(Flags.FLAG_NORMALIZED_PWLE_EFFECTS) @NonNull public static VibrationEffect.WaveformEnvelopeBuilder startWaveformEnvelope( @FloatRange(from = 0) float initialFrequencyHz) { return new WaveformEnvelopeBuilder(initialFrequencyHz); } /** * A builder for waveform effects described by its envelope. * Loading Loading @@ -1810,6 +1838,10 @@ public abstract class VibrationEffect implements Parcelable { private WaveformEnvelopeBuilder() {} private WaveformEnvelopeBuilder(float initialFrequency) { mLastFrequencyHz = initialFrequency; } /** * Adds a new control point to the end of this waveform envelope. * Loading Loading @@ -1841,7 +1873,7 @@ public abstract class VibrationEffect implements Parcelable { @FloatRange(from = 0, to = 1) float amplitude, @FloatRange(from = 0) float frequencyHz, int timeMillis) { if (mSegments.isEmpty()) { if (mLastFrequencyHz == 0) { mLastFrequencyHz = frequencyHz; } Loading
core/java/android/os/vibrator/PwlePoint.java 0 → 100644 +66 −0 Original line number Diff line number Diff line /* * Copyright (C) 2024 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 android.os.vibrator; import java.util.Objects; /** * A {@link PwlePoint} represents a single point in an envelope vibration effect. Defined by its * amplitude, frequency and time to transition to this point from the previous one in the envelope. * * @hide */ public final class PwlePoint { private final float mAmplitude; private final float mFrequencyHz; private final int mTimeMillis; /** @hide */ public PwlePoint(float amplitude, float frequencyHz, int timeMillis) { mAmplitude = amplitude; mFrequencyHz = frequencyHz; mTimeMillis = timeMillis; } public float getAmplitude() { return mAmplitude; } public float getFrequencyHz() { return mFrequencyHz; } public int getTimeMillis() { return mTimeMillis; } @Override public boolean equals(Object obj) { if (!(obj instanceof PwlePoint)) { return false; } PwlePoint other = (PwlePoint) obj; return Float.compare(mAmplitude, other.mAmplitude) == 0 && Float.compare(mFrequencyHz, other.mFrequencyHz) == 0 && mTimeMillis == other.mTimeMillis; } @Override public int hashCode() { return Objects.hash(mAmplitude, mFrequencyHz, mTimeMillis); } }
core/tests/vibrator/src/android/os/VibrationEffectTest.java +68 −0 Original line number Diff line number Diff line Loading @@ -425,6 +425,15 @@ public class VibrationEffectTest { .build(); assertNull(effect.computeCreateWaveformOffOnTimingsOrNull()); effect = VibrationEffect.startWaveformEnvelope(/*initialFrequency=*/ 60) .addControlPoint(/*amplitude=*/ 0.0f, /*frequencyHz=*/ 60f, /*timeMillis=*/ 20) .addControlPoint(/*amplitude=*/ 0.3f, /*frequencyHz=*/ 100f, /*timeMillis=*/ 50) .addControlPoint(/*amplitude=*/ 0.4f, /*frequencyHz=*/ 120f, /*timeMillis=*/ 80) .addControlPoint(/*amplitude=*/ 0.0f, /*frequencyHz=*/ 120f, /*timeMillis=*/ 40) .build(); assertNull(effect.computeCreateWaveformOffOnTimingsOrNull()); } @Test Loading Loading @@ -643,6 +652,14 @@ public class VibrationEffectTest { .build() .validate(); VibrationEffect.startWaveformEnvelope(/*initialFrequency=*/ 30) .addControlPoint(/*amplitude=*/ 0.0f, /*frequencyHz=*/ 60f, /*timeMillis=*/ 20) .addControlPoint(/*amplitude=*/ 0.3f, /*frequencyHz=*/ 100f, /*timeMillis=*/ 50) .addControlPoint(/*amplitude=*/ 0.4f, /*frequencyHz=*/ 120f, /*timeMillis=*/ 80) .addControlPoint(/*amplitude=*/ 0.0f, /*frequencyHz=*/ 120f, /*timeMillis=*/ 40) .build() .validate(); VibrationEffect.createRepeatingEffect( /*preamble=*/ VibrationEffect.startWaveformEnvelope() .addControlPoint(/*amplitude=*/ 0.0f, /*frequencyHz=*/ 60f, Loading Loading @@ -693,6 +710,34 @@ public class VibrationEffectTest { /*timeMillis=*/ 0) .build() .validate()); assertThrows(IllegalStateException.class, () -> VibrationEffect.startWaveformEnvelope(/*initialFrequency=*/30) .build().validate()); assertThrows(IllegalArgumentException.class, () -> VibrationEffect.startWaveformEnvelope(/*initialFrequency=*/30) .addControlPoint(/*amplitude=*/ -1.0f, /*frequencyHz=*/ 60f, /*timeMillis=*/ 20) .build() .validate()); assertThrows(IllegalArgumentException.class, () -> VibrationEffect.startWaveformEnvelope(/*initialFrequency=*/30) .addControlPoint(/*amplitude=*/ 1.1f, /*frequencyHz=*/ 60f, /*timeMillis=*/ 20) .build() .validate()); assertThrows(IllegalArgumentException.class, () -> VibrationEffect.startWaveformEnvelope(/*initialFrequency=*/30) .addControlPoint(/*amplitude=*/ 0.8f, /*frequencyHz=*/ 0f, /*timeMillis=*/ 20) .build() .validate()); assertThrows(IllegalArgumentException.class, () -> VibrationEffect.startWaveformEnvelope(/*initialFrequency=*/30) .addControlPoint(/*amplitude=*/ 0.8f, /*frequencyHz=*/ 100f, /*timeMillis=*/ 0) .build() .validate()); } @Test Loading Loading @@ -1331,6 +1376,11 @@ public class VibrationEffectTest { .addTransition(Duration.ofMillis(500), targetAmplitude(0)) .build() .isHapticFeedbackCandidate()); } @Test @EnableFlags(Flags.FLAG_NORMALIZED_PWLE_EFFECTS) public void testIsHapticFeedbackCandidate_longEnvelopeEffects_notCandidates() { assertFalse(VibrationEffect.startWaveformEnvelope() .addControlPoint(/*amplitude=*/ 0.0f, /*frequencyHz=*/ 60f, /*timeMillis=*/ 200) .addControlPoint(/*amplitude=*/ 0.3f, /*frequencyHz=*/ 100f, /*timeMillis=*/ 500) Loading @@ -1338,6 +1388,13 @@ public class VibrationEffectTest { .addControlPoint(/*amplitude=*/ 0.0f, /*frequencyHz=*/ 120f, /*timeMillis=*/ 400) .build() .isHapticFeedbackCandidate()); assertFalse(VibrationEffect.startWaveformEnvelope(/*initialFrequency=*/ 40) .addControlPoint(/*amplitude=*/ 0.0f, /*frequencyHz=*/ 60f, /*timeMillis=*/ 200) .addControlPoint(/*amplitude=*/ 0.3f, /*frequencyHz=*/ 100f, /*timeMillis=*/ 500) .addControlPoint(/*amplitude=*/ 0.4f, /*frequencyHz=*/ 120f, /*timeMillis=*/ 800) .addControlPoint(/*amplitude=*/ 0.0f, /*frequencyHz=*/ 120f, /*timeMillis=*/ 400) .build() .isHapticFeedbackCandidate()); } @Test Loading @@ -1351,12 +1408,23 @@ public class VibrationEffectTest { .addTransition(Duration.ofMillis(300), targetAmplitude(0)) .build() .isHapticFeedbackCandidate()); } @Test @EnableFlags(Flags.FLAG_NORMALIZED_PWLE_EFFECTS) public void testIsHapticFeedbackCandidate_shortEnvelopeEffects_areCandidates() { assertTrue(VibrationEffect.startWaveformEnvelope() .addControlPoint(/*amplitude=*/ 0.3f, /*frequencyHz=*/ 100f, /*timeMillis=*/ 500) .addControlPoint(/*amplitude=*/ 0.4f, /*frequencyHz=*/ 120f, /*timeMillis=*/ 400) .addControlPoint(/*amplitude=*/ 0.0f, /*frequencyHz=*/ 120f, /*timeMillis=*/ 100) .build() .isHapticFeedbackCandidate()); assertTrue(VibrationEffect.startWaveformEnvelope(/*initialFrequency=*/ 30) .addControlPoint(/*amplitude=*/ 0.3f, /*frequencyHz=*/ 100f, /*timeMillis=*/ 500) .addControlPoint(/*amplitude=*/ 0.4f, /*frequencyHz=*/ 120f, /*timeMillis=*/ 400) .addControlPoint(/*amplitude=*/ 0.0f, /*frequencyHz=*/ 120f, /*timeMillis=*/ 100) .build() .isHapticFeedbackCandidate()); } @Test Loading
services/core/java/com/android/server/vibrator/ComposePwleV2VibratorStep.java +23 −14 Original line number Diff line number Diff line Loading @@ -20,6 +20,7 @@ import android.annotation.NonNull; import android.os.Trace; import android.os.VibrationEffect; import android.os.vibrator.Flags; import android.os.vibrator.PwlePoint; import android.os.vibrator.PwleSegment; import android.os.vibrator.VibrationEffectSegment; import android.util.Slog; Loading Loading @@ -57,7 +58,7 @@ final class ComposePwleV2VibratorStep extends AbstractComposedVibratorStep { // Load the next PwleSegments to create a single composePwleV2 call to the vibrator, // limited to the vibrator's maximum envelope effect size. int limit = controller.getVibratorInfo().getMaxEnvelopeEffectSize(); List<PwleSegment> pwles = unrollPwleSegments(effect, segmentIndex, limit); List<PwlePoint> pwles = unrollPwleSegments(effect, segmentIndex, limit); if (pwles.isEmpty()) { Slog.w(VibrationThread.TAG, "Ignoring wrong segment for a ComposeEnvelopeStep: " Loading @@ -70,7 +71,7 @@ final class ComposePwleV2VibratorStep extends AbstractComposedVibratorStep { Slog.d(VibrationThread.TAG, "Compose " + pwles + " PWLEs on vibrator " + controller.getVibratorInfo().getId()); } PwleSegment[] pwlesArray = pwles.toArray(new PwleSegment[pwles.size()]); PwlePoint[] pwlesArray = pwles.toArray(new PwlePoint[pwles.size()]); long vibratorOnResult = controller.on(pwlesArray, getVibration().id); handleVibratorOnResult(vibratorOnResult); getVibration().stats.reportComposePwle(vibratorOnResult, pwlesArray); Loading @@ -82,9 +83,9 @@ final class ComposePwleV2VibratorStep extends AbstractComposedVibratorStep { } } private List<PwleSegment> unrollPwleSegments(VibrationEffect.Composed effect, int startIndex, private List<PwlePoint> unrollPwleSegments(VibrationEffect.Composed effect, int startIndex, int limit) { List<PwleSegment> segments = new ArrayList<>(limit); List<PwlePoint> pwlePoints = new ArrayList<>(limit); float bestBreakAmplitude = 1; int bestBreakPosition = limit; // Exclusive index. Loading @@ -93,7 +94,7 @@ final class ComposePwleV2VibratorStep extends AbstractComposedVibratorStep { // Loop once after reaching the limit to see if breaking it will really be necessary, then // apply the best break position found, otherwise return the full list as it fits the limit. for (int i = startIndex; segments.size() <= limit; i++) { for (int i = startIndex; pwlePoints.size() < limit; i++) { if (i == segmentCount) { if (repeatIndex >= 0) { i = repeatIndex; Loading @@ -104,12 +105,20 @@ final class ComposePwleV2VibratorStep extends AbstractComposedVibratorStep { } VibrationEffectSegment segment = effect.getSegments().get(i); if (segment instanceof PwleSegment pwleSegment) { segments.add(pwleSegment); if (pwlePoints.isEmpty()) { // The initial state is defined by the starting amplitude and frequency of the // first PwleSegment. The time parameter is set to zero to indicate this is // the initial condition without any ramp up time. pwlePoints.add(new PwlePoint(pwleSegment.getStartAmplitude(), pwleSegment.getStartFrequencyHz(), /*timeMillis=*/ 0)); } pwlePoints.add(new PwlePoint(pwleSegment.getEndAmplitude(), pwleSegment.getEndFrequencyHz(), (int) pwleSegment.getDuration())); if (isBetterBreakPosition(segments, bestBreakAmplitude, limit)) { if (isBetterBreakPosition(pwlePoints, bestBreakAmplitude, limit)) { // Mark this position as the best one so far to break a long waveform. bestBreakAmplitude = pwleSegment.getEndAmplitude(); bestBreakPosition = segments.size(); // Break after this pwle ends. bestBreakPosition = pwlePoints.size(); // Break after this pwle ends. } } else { // First non-pwle segment, stop collecting pwles. Loading @@ -117,21 +126,21 @@ final class ComposePwleV2VibratorStep extends AbstractComposedVibratorStep { } } return segments.size() > limit return pwlePoints.size() > limit // Remove excessive segments, using the best breaking position recorded. ? segments.subList(0, bestBreakPosition) ? pwlePoints.subList(0, bestBreakPosition) // Return all collected pwle segments. : segments; : pwlePoints; } /** * Returns true if the current segment list represents a better break position for a PWLE, * given the current amplitude being used for breaking it at a smaller size and the size limit. */ private boolean isBetterBreakPosition(List<PwleSegment> segments, private boolean isBetterBreakPosition(List<PwlePoint> segments, float currentBestBreakAmplitude, int limit) { PwleSegment lastSegment = segments.get(segments.size() - 1); float breakAmplitudeCandidate = lastSegment.getEndAmplitude(); PwlePoint lastSegment = segments.get(segments.size() - 1); float breakAmplitudeCandidate = lastSegment.getAmplitude(); int breakPositionCandidate = segments.size(); if (breakPositionCandidate > limit) { Loading