diff --git a/core/api/test-current.txt b/core/api/test-current.txt index ae1cbf77dd8a7..4a1b7e718fd48 100644 --- a/core/api/test-current.txt +++ b/core/api/test-current.txt @@ -1711,35 +1711,16 @@ package android.os { field public static final int[] RINGTONES; } - public static class VibrationEffect.OneShot extends android.os.VibrationEffect implements android.os.Parcelable { - ctor public VibrationEffect.OneShot(android.os.Parcel); - ctor public VibrationEffect.OneShot(long, int); - method public int getAmplitude(); - method public long getDuration(); - method public void writeToParcel(android.os.Parcel, int); - field @NonNull public static final android.os.Parcelable.Creator CREATOR; - } - - public static class VibrationEffect.Prebaked extends android.os.VibrationEffect implements android.os.Parcelable { - ctor public VibrationEffect.Prebaked(android.os.Parcel); - ctor public VibrationEffect.Prebaked(int, boolean, int); - method public long getDuration(); - method public int getEffectStrength(); - method public int getId(); - method public boolean shouldFallback(); - method public void writeToParcel(android.os.Parcel, int); - field @NonNull public static final android.os.Parcelable.Creator CREATOR; - } - - public static class VibrationEffect.Waveform extends android.os.VibrationEffect implements android.os.Parcelable { - ctor public VibrationEffect.Waveform(android.os.Parcel); - ctor public VibrationEffect.Waveform(long[], int[], int); - method public int[] getAmplitudes(); + public static final class VibrationEffect.Composed extends android.os.VibrationEffect { + method @NonNull public android.os.VibrationEffect.Composed applyEffectStrength(int); method public long getDuration(); method public int getRepeatIndex(); - method public long[] getTimings(); - method public void writeToParcel(android.os.Parcel, int); - field @NonNull public static final android.os.Parcelable.Creator CREATOR; + method @NonNull public java.util.List getSegments(); + method @NonNull public android.os.VibrationEffect.Composed resolve(int); + method @NonNull public android.os.VibrationEffect.Composed scale(float); + method public void validate(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator CREATOR; } public class VintfObject { @@ -1856,6 +1837,63 @@ package android.os.strictmode { } +package android.os.vibrator { + + public final class PrebakedSegment extends android.os.vibrator.VibrationEffectSegment { + method @NonNull public android.os.vibrator.PrebakedSegment applyEffectStrength(int); + method public int describeContents(); + method public long getDuration(); + method public int getEffectId(); + method public int getEffectStrength(); + method public boolean hasNonZeroAmplitude(); + method @NonNull public android.os.vibrator.PrebakedSegment resolve(int); + method @NonNull public android.os.vibrator.PrebakedSegment scale(float); + method public boolean shouldFallback(); + method public void validate(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator CREATOR; + } + + public final class PrimitiveSegment extends android.os.vibrator.VibrationEffectSegment { + method @NonNull public android.os.vibrator.PrimitiveSegment applyEffectStrength(int); + method public int describeContents(); + method public int getDelay(); + method public long getDuration(); + method public int getPrimitiveId(); + method public float getScale(); + method public boolean hasNonZeroAmplitude(); + method @NonNull public android.os.vibrator.PrimitiveSegment resolve(int); + method @NonNull public android.os.vibrator.PrimitiveSegment scale(float); + method public void validate(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator CREATOR; + } + + public final class StepSegment extends android.os.vibrator.VibrationEffectSegment { + method @NonNull public android.os.vibrator.StepSegment applyEffectStrength(int); + method public int describeContents(); + method public float getAmplitude(); + method public long getDuration(); + method public boolean hasNonZeroAmplitude(); + method @NonNull public android.os.vibrator.StepSegment resolve(int); + method @NonNull public android.os.vibrator.StepSegment scale(float); + method public void validate(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator CREATOR; + } + + public abstract class VibrationEffectSegment implements android.os.Parcelable { + method @NonNull public abstract T applyEffectStrength(int); + method public abstract long getDuration(); + method public abstract boolean hasNonZeroAmplitude(); + method @NonNull public abstract T resolve(int); + method @NonNull public abstract T scale(float); + method public abstract void validate(); + field @NonNull public static final android.os.Parcelable.Creator CREATOR; + } + +} + package android.permission { public final class PermissionControllerManager { diff --git a/core/api/test-lint-baseline.txt b/core/api/test-lint-baseline.txt index a536efb2b4881..1833ed50ce4fe 100644 --- a/core/api/test-lint-baseline.txt +++ b/core/api/test-lint-baseline.txt @@ -1515,30 +1515,6 @@ MissingNullability: android.os.VibrationEffect#get(int): MissingNullability: android.os.VibrationEffect#get(int, boolean): -MissingNullability: android.os.VibrationEffect.OneShot#OneShot(android.os.Parcel) parameter #0: - -MissingNullability: android.os.VibrationEffect.OneShot#scale(float, int): - -MissingNullability: android.os.VibrationEffect.OneShot#writeToParcel(android.os.Parcel, int) parameter #0: - -MissingNullability: android.os.VibrationEffect.Prebaked#Prebaked(android.os.Parcel) parameter #0: - -MissingNullability: android.os.VibrationEffect.Prebaked#writeToParcel(android.os.Parcel, int) parameter #0: - -MissingNullability: android.os.VibrationEffect.Waveform#Waveform(android.os.Parcel) parameter #0: - -MissingNullability: android.os.VibrationEffect.Waveform#Waveform(long[], int[], int) parameter #0: - -MissingNullability: android.os.VibrationEffect.Waveform#Waveform(long[], int[], int) parameter #1: - -MissingNullability: android.os.VibrationEffect.Waveform#getAmplitudes(): - -MissingNullability: android.os.VibrationEffect.Waveform#getTimings(): - -MissingNullability: android.os.VibrationEffect.Waveform#scale(float, int): - -MissingNullability: android.os.VibrationEffect.Waveform#writeToParcel(android.os.Parcel, int) parameter #0: - MissingNullability: android.os.VintfObject#getHalNamesAndVersions(): MissingNullability: android.os.VintfObject#getSepolicyVersion(): @@ -2739,12 +2715,6 @@ ParcelConstructor: android.os.IncidentReportArgs#IncidentReportArgs(android.os.P ParcelConstructor: android.os.StrictMode.ViolationInfo#ViolationInfo(android.os.Parcel): -ParcelConstructor: android.os.VibrationEffect.OneShot#OneShot(android.os.Parcel): - -ParcelConstructor: android.os.VibrationEffect.Prebaked#Prebaked(android.os.Parcel): - -ParcelConstructor: android.os.VibrationEffect.Waveform#Waveform(android.os.Parcel): - ParcelConstructor: android.os.health.HealthStatsParceler#HealthStatsParceler(android.os.Parcel): ParcelConstructor: android.service.notification.SnoozeCriterion#SnoozeCriterion(android.os.Parcel): @@ -2773,12 +2743,6 @@ ParcelCreator: android.net.metrics.RaEvent: ParcelCreator: android.net.metrics.ValidationProbeEvent: -ParcelCreator: android.os.VibrationEffect.OneShot: - -ParcelCreator: android.os.VibrationEffect.Prebaked: - -ParcelCreator: android.os.VibrationEffect.Waveform: - ParcelCreator: android.service.autofill.InternalOnClickAction: ParcelCreator: android.service.autofill.InternalSanitizer: @@ -2797,12 +2761,6 @@ ParcelNotFinal: android.net.metrics.IpConnectivityLog.Event: ParcelNotFinal: android.os.IncidentManager.IncidentReport: -ParcelNotFinal: android.os.VibrationEffect.OneShot: - -ParcelNotFinal: android.os.VibrationEffect.Prebaked: - -ParcelNotFinal: android.os.VibrationEffect.Waveform: - ParcelNotFinal: android.os.health.HealthStatsParceler: ParcelNotFinal: android.service.autofill.InternalOnClickAction: diff --git a/core/java/android/os/SystemVibrator.java b/core/java/android/os/SystemVibrator.java index b42a495ece560..219912c24e70f 100644 --- a/core/java/android/os/SystemVibrator.java +++ b/core/java/android/os/SystemVibrator.java @@ -218,7 +218,7 @@ public class SystemVibrator extends Vibrator { @Override public boolean[] arePrimitivesSupported( - @NonNull @VibrationEffect.Composition.Primitive int... primitiveIds) { + @NonNull @VibrationEffect.Composition.PrimitiveType int... primitiveIds) { boolean[] supported = new boolean[primitiveIds.length]; if (mVibratorManager == null) { Log.w(TAG, "Failed to check supported primitives; no vibrator manager."); diff --git a/core/java/android/os/SystemVibratorManager.java b/core/java/android/os/SystemVibratorManager.java index b528eb157e368..841aad556d6ad 100644 --- a/core/java/android/os/SystemVibratorManager.java +++ b/core/java/android/os/SystemVibratorManager.java @@ -211,7 +211,7 @@ public class SystemVibratorManager extends VibratorManager { @Override public boolean[] arePrimitivesSupported( - @NonNull @VibrationEffect.Composition.Primitive int... primitiveIds) { + @NonNull @VibrationEffect.Composition.PrimitiveType int... primitiveIds) { boolean[] supported = new boolean[primitiveIds.length]; for (int i = 0; i < primitiveIds.length; i++) { supported[i] = mVibratorInfo.isPrimitiveSupported(primitiveIds[i]); diff --git a/core/java/android/os/VibrationAttributes.java b/core/java/android/os/VibrationAttributes.java index 217f1785bcd60..cec323f8b4230 100644 --- a/core/java/android/os/VibrationAttributes.java +++ b/core/java/android/os/VibrationAttributes.java @@ -21,6 +21,8 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.TestApi; import android.media.AudioAttributes; +import android.os.vibrator.PrebakedSegment; +import android.os.vibrator.VibrationEffectSegment; import android.util.Slog; import java.lang.annotation.Retention; @@ -330,9 +332,9 @@ public final class VibrationAttributes implements Parcelable { private void applyHapticFeedbackHeuristics(@Nullable VibrationEffect effect) { if (effect != null) { - if (mUsage == USAGE_UNKNOWN && effect instanceof VibrationEffect.Prebaked) { - VibrationEffect.Prebaked prebaked = (VibrationEffect.Prebaked) effect; - switch (prebaked.getId()) { + PrebakedSegment prebaked = extractPrebakedSegment(effect); + if (mUsage == USAGE_UNKNOWN && prebaked != null) { + switch (prebaked.getEffectId()) { case VibrationEffect.EFFECT_CLICK: case VibrationEffect.EFFECT_DOUBLE_CLICK: case VibrationEffect.EFFECT_HEAVY_CLICK: @@ -355,6 +357,20 @@ public final class VibrationAttributes implements Parcelable { } } + @Nullable + private PrebakedSegment extractPrebakedSegment(VibrationEffect effect) { + if (effect instanceof VibrationEffect.Composed) { + VibrationEffect.Composed composed = (VibrationEffect.Composed) effect; + if (composed.getSegments().size() == 1) { + VibrationEffectSegment segment = composed.getSegments().get(0); + if (segment instanceof PrebakedSegment) { + return (PrebakedSegment) segment; + } + } + } + return null; + } + private void setUsage(@NonNull AudioAttributes audio) { mOriginalAudioUsage = audio.getUsage(); switch (audio.getUsage()) { diff --git a/core/java/android/os/VibrationEffect.aidl b/core/java/android/os/VibrationEffect.aidl index 89478fac2f1a2..6311760a4bfc8 100644 --- a/core/java/android/os/VibrationEffect.aidl +++ b/core/java/android/os/VibrationEffect.aidl @@ -17,4 +17,3 @@ package android.os; parcelable VibrationEffect; -parcelable VibrationEffect.Composition.PrimitiveEffect; \ No newline at end of file diff --git a/core/java/android/os/VibrationEffect.java b/core/java/android/os/VibrationEffect.java index 0199fad067cb6..95b5e856afec6 100644 --- a/core/java/android/os/VibrationEffect.java +++ b/core/java/android/os/VibrationEffect.java @@ -28,6 +28,10 @@ import android.content.Context; import android.hardware.vibrator.V1_0.EffectStrength; import android.hardware.vibrator.V1_3.Effect; import android.net.Uri; +import android.os.vibrator.PrebakedSegment; +import android.os.vibrator.PrimitiveSegment; +import android.os.vibrator.StepSegment; +import android.os.vibrator.VibrationEffectSegment; import android.util.MathUtils; import com.android.internal.util.Preconditions; @@ -45,11 +49,6 @@ import java.util.Objects; * These effects may be any number of things, from single shot vibrations to complex waveforms. */ public abstract class VibrationEffect implements Parcelable { - private static final int PARCEL_TOKEN_ONE_SHOT = 1; - private static final int PARCEL_TOKEN_WAVEFORM = 2; - private static final int PARCEL_TOKEN_EFFECT = 3; - private static final int PARCEL_TOKEN_COMPOSITION = 4; - // Stevens' coefficient to scale the perceived vibration intensity. private static final float SCALE_GAMMA = 0.65f; @@ -181,9 +180,7 @@ public abstract class VibrationEffect implements Parcelable { * @return The desired effect. */ public static VibrationEffect createOneShot(long milliseconds, int amplitude) { - VibrationEffect effect = new OneShot(milliseconds, amplitude); - effect.validate(); - return effect; + return createWaveform(new long[]{milliseconds}, new int[]{amplitude}, -1 /* repeat */); } /** @@ -243,7 +240,19 @@ public abstract class VibrationEffect implements Parcelable { * @return The desired effect. */ public static VibrationEffect createWaveform(long[] timings, int[] amplitudes, int repeat) { - VibrationEffect effect = new Waveform(timings, amplitudes, repeat); + if (timings.length != amplitudes.length) { + throw new IllegalArgumentException( + "timing and amplitude arrays must be of equal length" + + " (timings.length=" + timings.length + + ", amplitudes.length=" + amplitudes.length + ")"); + } + List segments = new ArrayList<>(); + for (int i = 0; i < timings.length; i++) { + float parsedAmplitude = amplitudes[i] == DEFAULT_AMPLITUDE + ? DEFAULT_AMPLITUDE : (float) amplitudes[i] / MAX_AMPLITUDE; + segments.add(new StepSegment(parsedAmplitude, (int) timings[i])); + } + VibrationEffect effect = new Composed(segments, repeat); effect.validate(); return effect; } @@ -317,7 +326,8 @@ public abstract class VibrationEffect implements Parcelable { */ @TestApi public static VibrationEffect get(int effectId, boolean fallback) { - VibrationEffect effect = new Prebaked(effectId, fallback, EffectStrength.MEDIUM); + VibrationEffect effect = new Composed( + new PrebakedSegment(effectId, fallback, EffectStrength.MEDIUM)); effect.validate(); return effect; } @@ -428,32 +438,28 @@ public abstract class VibrationEffect implements Parcelable { public abstract T scale(float scaleFactor); /** - * Scale given vibration intensity by the given factor. - * - * @param amplitude amplitude of the effect, must be between 0 and MAX_AMPLITUDE - * @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 + * Applies given effect strength to prebaked effects represented by one of + * VibrationEffect.EFFECT_*. * + * @param effectStrength new effect strength to be applied, one of + * VibrationEffect.EFFECT_STRENGTH_*. + * @return this if there is no change to this effect, or a copy of this effect with applied + * effect strength otherwise. * @hide */ - protected static int scale(int amplitude, float scaleFactor) { - if (amplitude == 0) { - return 0; - } - int scaled = (int) (scale((float) amplitude / MAX_AMPLITUDE, scaleFactor) * MAX_AMPLITUDE); - return MathUtils.constrain(scaled, 1, MAX_AMPLITUDE); + public T applyEffectStrength(int effectStrength) { + return (T) this; } /** * Scale given vibration intensity by the given factor. * - * @param intensity relative intensity of the effect, must be between 0 and 1 + * @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 - * * @hide */ - protected static float scale(float intensity, float scaleFactor) { + public static float scale(float intensity, float scaleFactor) { // 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); @@ -516,545 +522,152 @@ public abstract class VibrationEffect implements Parcelable { } } - /** @hide */ + /** + * Implementation of {@link VibrationEffect} described by a composition of one or more + * {@link VibrationEffectSegment}, with an optional index to represent repeating effects. + * + * @hide + */ @TestApi - public static class OneShot extends VibrationEffect implements Parcelable { - private final long mDuration; - private final int mAmplitude; + public static final class Composed extends VibrationEffect { + private final ArrayList mSegments; + private final int mRepeatIndex; - public OneShot(Parcel in) { - mDuration = in.readLong(); - mAmplitude = in.readInt(); + Composed(@NonNull Parcel in) { + this(in.readArrayList(VibrationEffectSegment.class.getClassLoader()), in.readInt()); } - public OneShot(long milliseconds, int amplitude) { - mDuration = milliseconds; - mAmplitude = amplitude; - } - - @Override - public long getDuration() { - return mDuration; - } - - public int getAmplitude() { - return mAmplitude; + Composed(@NonNull VibrationEffectSegment segment) { + this(Arrays.asList(segment), /* repeatIndex= */ -1); } /** @hide */ - @Override - public OneShot scale(float scaleFactor) { - if (scaleFactor == 1f || mAmplitude == DEFAULT_AMPLITUDE) { - // Just return this if there's no scaling to be done or if amplitude is not yet set. - return this; - } - return new OneShot(mDuration, scale(mAmplitude, scaleFactor)); + public Composed(@NonNull List segments, int repeatIndex) { + super(); + mSegments = new ArrayList<>(segments); + mRepeatIndex = repeatIndex; } - /** @hide */ - @Override - public OneShot resolve(int defaultAmplitude) { - if (defaultAmplitude > MAX_AMPLITUDE || defaultAmplitude <= 0) { - throw new IllegalArgumentException( - "amplitude must be between 1 and 255 inclusive (amplitude=" - + defaultAmplitude + ")"); - } - if (mAmplitude == DEFAULT_AMPLITUDE) { - return new OneShot(mDuration, defaultAmplitude); - } - return this; - } - - /** @hide */ - @Override - public void validate() { - if (mAmplitude < -1 || mAmplitude == 0 || mAmplitude > 255) { - throw new IllegalArgumentException( - "amplitude must either be DEFAULT_AMPLITUDE, " - + "or between 1 and 255 inclusive (amplitude=" + mAmplitude + ")"); - } - if (mDuration <= 0) { - throw new IllegalArgumentException( - "duration must be positive (duration=" + mDuration + ")"); - } - } - - @Override - public boolean equals(@Nullable Object o) { - if (!(o instanceof VibrationEffect.OneShot)) { - return false; - } - VibrationEffect.OneShot other = (VibrationEffect.OneShot) o; - return other.mDuration == mDuration && other.mAmplitude == mAmplitude; - } - - @Override - public int hashCode() { - int result = 17; - result += 37 * (int) mDuration; - result += 37 * mAmplitude; - return result; - } - - @Override - public String toString() { - return "OneShot{mDuration=" + mDuration + ", mAmplitude=" + mAmplitude + "}"; - } - - @Override - public void writeToParcel(Parcel out, int flags) { - out.writeInt(PARCEL_TOKEN_ONE_SHOT); - out.writeLong(mDuration); - out.writeInt(mAmplitude); - } - - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) - public static final @android.annotation.NonNull Parcelable.Creator CREATOR = - new Parcelable.Creator() { - @Override - public OneShot createFromParcel(Parcel in) { - // Skip the type token - in.readInt(); - return new OneShot(in); - } - @Override - public OneShot[] newArray(int size) { - return new OneShot[size]; - } - }; - } - - /** @hide */ - @TestApi - public static class Waveform extends VibrationEffect implements Parcelable { - private final long[] mTimings; - private final int[] mAmplitudes; - private final int mRepeat; - - public Waveform(Parcel in) { - this(in.createLongArray(), in.createIntArray(), in.readInt()); - } - - public Waveform(long[] timings, int[] amplitudes, int repeat) { - mTimings = new long[timings.length]; - System.arraycopy(timings, 0, mTimings, 0, timings.length); - mAmplitudes = new int[amplitudes.length]; - System.arraycopy(amplitudes, 0, mAmplitudes, 0, amplitudes.length); - mRepeat = repeat; - } - - public long[] getTimings() { - return mTimings; - } - - public int[] getAmplitudes() { - return mAmplitudes; + @NonNull + public List getSegments() { + return mSegments; } public int getRepeatIndex() { - return mRepeat; + return mRepeatIndex; + } + + @Override + public void validate() { + int segmentCount = mSegments.size(); + boolean hasNonZeroDuration = false; + boolean hasNonZeroAmplitude = false; + for (int i = 0; i < segmentCount; i++) { + VibrationEffectSegment segment = mSegments.get(i); + segment.validate(); + // A segment with unknown duration = -1 still counts as a non-zero duration. + hasNonZeroDuration |= segment.getDuration() != 0; + hasNonZeroAmplitude |= segment.hasNonZeroAmplitude(); + } + if (!hasNonZeroDuration) { + throw new IllegalArgumentException("at least one timing must be non-zero" + + " (segments=" + mSegments + ")"); + } + if (!hasNonZeroAmplitude) { + throw new IllegalArgumentException("at least one amplitude must be non-zero" + + " (segments=" + mSegments + ")"); + } + if (mRepeatIndex != -1) { + Preconditions.checkArgumentInRange(mRepeatIndex, 0, segmentCount - 1, + "repeat index must be within the bounds of the segments (segments.length=" + + segmentCount + ", index=" + mRepeatIndex + ")"); + } } @Override public long getDuration() { - if (mRepeat >= 0) { + if (mRepeatIndex >= 0) { return Long.MAX_VALUE; } - long duration = 0; - for (long d : mTimings) { - duration += d; - } - return duration; - } - - /** @hide */ - @Override - public Waveform scale(float scaleFactor) { - if (scaleFactor == 1f) { - // Just return this if there's no scaling to be done. - return this; - } - boolean scaled = false; - int[] scaledAmplitudes = Arrays.copyOf(mAmplitudes, mAmplitudes.length); - for (int i = 0; i < scaledAmplitudes.length; i++) { - if (scaledAmplitudes[i] == DEFAULT_AMPLITUDE) { - // Skip amplitudes that are not set. - continue; + int segmentCount = mSegments.size(); + long totalDuration = 0; + for (int i = 0; i < segmentCount; i++) { + long segmentDuration = mSegments.get(i).getDuration(); + if (segmentDuration < 0) { + return segmentDuration; } - scaled = true; - scaledAmplitudes[i] = scale(scaledAmplitudes[i], scaleFactor); + totalDuration += segmentDuration; } - if (!scaled) { - // Just return this if no scaling was done. - return this; - } - return new Waveform(mTimings, scaledAmplitudes, mRepeat); + return totalDuration; } - /** @hide */ - @Override - public Waveform resolve(int defaultAmplitude) { - if (defaultAmplitude > MAX_AMPLITUDE || defaultAmplitude < 0) { - throw new IllegalArgumentException( - "Amplitude is negative or greater than MAX_AMPLITUDE"); - } - boolean resolved = false; - int[] resolvedAmplitudes = Arrays.copyOf(mAmplitudes, mAmplitudes.length); - for (int i = 0; i < resolvedAmplitudes.length; i++) { - if (resolvedAmplitudes[i] == DEFAULT_AMPLITUDE) { - resolvedAmplitudes[i] = defaultAmplitude; - resolved = true; - } - } - if (!resolved) { - return this; - } - return new Waveform(mTimings, resolvedAmplitudes, mRepeat); - } - - /** @hide */ - @Override - public void validate() { - if (mTimings.length != mAmplitudes.length) { - throw new IllegalArgumentException( - "timing and amplitude arrays must be of equal length" - + " (timings.length=" + mTimings.length - + ", amplitudes.length=" + mAmplitudes.length + ")"); - } - if (!hasNonZeroEntry(mTimings)) { - throw new IllegalArgumentException("at least one timing must be non-zero" - + " (timings=" + Arrays.toString(mTimings) + ")"); - } - for (long timing : mTimings) { - if (timing < 0) { - throw new IllegalArgumentException("timings must all be >= 0" - + " (timings=" + Arrays.toString(mTimings) + ")"); - } - } - for (int amplitude : mAmplitudes) { - if (amplitude < -1 || amplitude > 255) { - throw new IllegalArgumentException( - "amplitudes must all be DEFAULT_AMPLITUDE or between 0 and 255" - + " (amplitudes=" + Arrays.toString(mAmplitudes) + ")"); - } - } - if (mRepeat < -1 || mRepeat >= mTimings.length) { - throw new IllegalArgumentException( - "repeat index must be within the bounds of the timings array" - + " (timings.length=" + mTimings.length + ", index=" + mRepeat + ")"); - } - } - - @Override - public boolean equals(@Nullable Object o) { - if (!(o instanceof VibrationEffect.Waveform)) { - return false; - } - VibrationEffect.Waveform other = (VibrationEffect.Waveform) o; - return Arrays.equals(mTimings, other.mTimings) - && Arrays.equals(mAmplitudes, other.mAmplitudes) - && mRepeat == other.mRepeat; - } - - @Override - public int hashCode() { - int result = 17; - result += 37 * Arrays.hashCode(mTimings); - result += 37 * Arrays.hashCode(mAmplitudes); - result += 37 * mRepeat; - return result; - } - - @Override - public String toString() { - return "Waveform{mTimings=" + Arrays.toString(mTimings) - + ", mAmplitudes=" + Arrays.toString(mAmplitudes) - + ", mRepeat=" + mRepeat - + "}"; - } - - @Override - public void writeToParcel(Parcel out, int flags) { - out.writeInt(PARCEL_TOKEN_WAVEFORM); - out.writeLongArray(mTimings); - out.writeIntArray(mAmplitudes); - out.writeInt(mRepeat); - } - - private static boolean hasNonZeroEntry(long[] vals) { - for (long val : vals) { - if (val != 0) { - return true; - } - } - return false; - } - - - public static final @android.annotation.NonNull Parcelable.Creator CREATOR = - new Parcelable.Creator() { - @Override - public Waveform createFromParcel(Parcel in) { - // Skip the type token - in.readInt(); - return new Waveform(in); - } - @Override - public Waveform[] newArray(int size) { - return new Waveform[size]; - } - }; - } - - /** @hide */ - @TestApi - public static class Prebaked extends VibrationEffect implements Parcelable { - private final int mEffectId; - private final boolean mFallback; - private final int mEffectStrength; - @Nullable - private final VibrationEffect mFallbackEffect; - - public Prebaked(Parcel in) { - mEffectId = in.readInt(); - mFallback = in.readByte() != 0; - mEffectStrength = in.readInt(); - mFallbackEffect = in.readParcelable(VibrationEffect.class.getClassLoader()); - } - - public Prebaked(int effectId, boolean fallback, int effectStrength) { - mEffectId = effectId; - mFallback = fallback; - mEffectStrength = effectStrength; - mFallbackEffect = null; - } - - /** @hide */ - public Prebaked(int effectId, int effectStrength, @NonNull VibrationEffect fallbackEffect) { - mEffectId = effectId; - mFallback = true; - mEffectStrength = effectStrength; - mFallbackEffect = fallbackEffect; - } - - public int getId() { - return mEffectId; - } - - /** - * Whether the effect should fall back to a generic pattern if there's no hardware specific - * implementation of it. - */ - public boolean shouldFallback() { - return mFallback; - } - - @Override - public long getDuration() { - return -1; - } - - /** @hide */ - @Override - public Prebaked resolve(int defaultAmplitude) { - if (mFallbackEffect != null) { - VibrationEffect resolvedFallback = mFallbackEffect.resolve(defaultAmplitude); - if (!mFallbackEffect.equals(resolvedFallback)) { - return new Prebaked(mEffectId, mEffectStrength, resolvedFallback); - } - } - return this; - } - - /** @hide */ - @Override - public Prebaked scale(float scaleFactor) { - if (mFallbackEffect != null) { - VibrationEffect scaledFallback = mFallbackEffect.scale(scaleFactor); - if (!mFallbackEffect.equals(scaledFallback)) { - return new Prebaked(mEffectId, mEffectStrength, scaledFallback); - } - } - // Prebaked effect strength cannot be scaled with this method. - return this; - } - - /** - * Set the effect strength. - */ - public int getEffectStrength() { - return mEffectStrength; - } - - /** - * Return the fallback effect, if set. - * - * @hide - */ - @Nullable - public VibrationEffect getFallbackEffect() { - return mFallbackEffect; - } - - private static boolean isValidEffectStrength(int strength) { - switch (strength) { - case EffectStrength.LIGHT: - case EffectStrength.MEDIUM: - case EffectStrength.STRONG: - return true; - default: - return false; - } - } - - /** @hide */ - @Override - public void validate() { - switch (mEffectId) { - case EFFECT_CLICK: - case EFFECT_DOUBLE_CLICK: - case EFFECT_TICK: - case EFFECT_TEXTURE_TICK: - case EFFECT_THUD: - case EFFECT_POP: - case EFFECT_HEAVY_CLICK: - break; - default: - if (mEffectId < RINGTONES[0] || mEffectId > RINGTONES[RINGTONES.length - 1]) { - throw new IllegalArgumentException( - "Unknown prebaked effect type (value=" + mEffectId + ")"); - } - } - if (!isValidEffectStrength(mEffectStrength)) { - throw new IllegalArgumentException( - "Unknown prebaked effect strength (value=" + mEffectStrength + ")"); - } - } - - @Override - public boolean equals(@Nullable Object o) { - if (!(o instanceof VibrationEffect.Prebaked)) { - return false; - } - VibrationEffect.Prebaked other = (VibrationEffect.Prebaked) o; - return mEffectId == other.mEffectId - && mFallback == other.mFallback - && mEffectStrength == other.mEffectStrength - && Objects.equals(mFallbackEffect, other.mFallbackEffect); - } - - @Override - public int hashCode() { - return Objects.hash(mEffectId, mFallback, mEffectStrength, mFallbackEffect); - } - - @Override - public String toString() { - return "Prebaked{mEffectId=" + effectIdToString(mEffectId) - + ", mEffectStrength=" + effectStrengthToString(mEffectStrength) - + ", mFallback=" + mFallback - + ", mFallbackEffect=" + mFallbackEffect - + "}"; - } - - - @Override - public void writeToParcel(Parcel out, int flags) { - out.writeInt(PARCEL_TOKEN_EFFECT); - out.writeInt(mEffectId); - out.writeByte((byte) (mFallback ? 1 : 0)); - out.writeInt(mEffectStrength); - out.writeParcelable(mFallbackEffect, flags); - } - - public static final @NonNull Parcelable.Creator CREATOR = - new Parcelable.Creator() { - @Override - public Prebaked createFromParcel(Parcel in) { - // Skip the type token - in.readInt(); - return new Prebaked(in); - } - @Override - public Prebaked[] newArray(int size) { - return new Prebaked[size]; - } - }; - } - - /** @hide */ - public static final class Composed extends VibrationEffect implements Parcelable { - private final ArrayList mPrimitiveEffects; - - /** - * @hide - */ - @SuppressWarnings("unchecked") - public Composed(@NonNull Parcel in) { - this(in.readArrayList(Composed.class.getClassLoader())); - } - - /** - * @hide - */ - public Composed(List effects) { - mPrimitiveEffects = new ArrayList<>(Objects.requireNonNull(effects)); - } - - /** - * @hide - */ @NonNull - public List getPrimitiveEffects() { - return mPrimitiveEffects; - } - @Override - public long getDuration() { - return -1; + public Composed resolve(int defaultAmplitude) { + int segmentCount = mSegments.size(); + ArrayList resolvedSegments = new ArrayList<>(segmentCount); + for (int i = 0; i < segmentCount; i++) { + resolvedSegments.add(mSegments.get(i).resolve(defaultAmplitude)); + } + if (resolvedSegments.equals(mSegments)) { + return this; + } + Composed resolved = new Composed(resolvedSegments, mRepeatIndex); + resolved.validate(); + return resolved; } - /** @hide */ - @Override - public VibrationEffect resolve(int defaultAmplitude) { - // Primitive effects already have default primitive intensity set, so ignore this. - return this; - } - - /** @hide */ + @NonNull @Override public Composed scale(float scaleFactor) { - if (scaleFactor == 1f) { - // Just return this if there's no scaling to be done. + int segmentCount = mSegments.size(); + ArrayList scaledSegments = new ArrayList<>(segmentCount); + for (int i = 0; i < segmentCount; i++) { + scaledSegments.add(mSegments.get(i).scale(scaleFactor)); + } + if (scaledSegments.equals(mSegments)) { return this; } - final int primitiveCount = mPrimitiveEffects.size(); - List scaledPrimitives = new ArrayList<>(); - for (int i = 0; i < primitiveCount; i++) { - Composition.PrimitiveEffect primitive = mPrimitiveEffects.get(i); - scaledPrimitives.add(new Composition.PrimitiveEffect( - primitive.id, scale(primitive.scale, scaleFactor), primitive.delay)); - } - return new Composed(scaledPrimitives); + Composed scaled = new Composed(scaledSegments, mRepeatIndex); + scaled.validate(); + return scaled; } - /** @hide */ + @NonNull @Override - public void validate() { - final int primitiveCount = mPrimitiveEffects.size(); - for (int i = 0; i < primitiveCount; i++) { - Composition.PrimitiveEffect primitive = mPrimitiveEffects.get(i); - Composition.checkPrimitive(primitive.id); - Preconditions.checkArgumentInRange(primitive.scale, 0.0f, 1.0f, "scale"); - Preconditions.checkArgumentNonNegative(primitive.delay, - "Primitive delay must be zero or positive"); + public Composed applyEffectStrength(int effectStrength) { + int segmentCount = mSegments.size(); + ArrayList scaledSegments = new ArrayList<>(segmentCount); + for (int i = 0; i < segmentCount; i++) { + scaledSegments.add(mSegments.get(i).applyEffectStrength(effectStrength)); } + if (scaledSegments.equals(mSegments)) { + return this; + } + Composed scaled = new Composed(scaledSegments, mRepeatIndex); + scaled.validate(); + return scaled; } @Override - public void writeToParcel(@NonNull Parcel out, int flags) { - out.writeInt(PARCEL_TOKEN_COMPOSITION); - out.writeList(mPrimitiveEffects); + public boolean equals(@Nullable Object o) { + if (!(o instanceof Composed)) { + return false; + } + Composed other = (Composed) o; + return mSegments.equals(other.mSegments) && mRepeatIndex == other.mRepeatIndex; + } + + @Override + public int hashCode() { + return Objects.hash(mSegments, mRepeatIndex); + } + + @Override + public String toString() { + return "Composed{segments=" + mSegments + + ", repeat=" + mRepeatIndex + + "}"; } @Override @@ -1063,34 +676,20 @@ public abstract class VibrationEffect implements Parcelable { } @Override - public boolean equals(@Nullable Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - Composed composed = (Composed) o; - return mPrimitiveEffects.equals(composed.mPrimitiveEffects); + public void writeToParcel(@NonNull Parcel out, int flags) { + out.writeList(mSegments); + out.writeInt(mRepeatIndex); } - @Override - public int hashCode() { - return Objects.hash(mPrimitiveEffects); - } - - @Override - public String toString() { - return "Composed{mPrimitiveEffects=" + mPrimitiveEffects + '}'; - } - - public static final @NonNull Parcelable.Creator CREATOR = - new Parcelable.Creator() { + @NonNull + public static final Creator CREATOR = + new Creator() { @Override - public Composed createFromParcel(@NonNull Parcel in) { - // Skip the type token - in.readInt(); + public Composed createFromParcel(Parcel in) { return new Composed(in); } @Override - @NonNull public Composed[] newArray(int size) { return new Composed[size]; } @@ -1115,7 +714,7 @@ public abstract class VibrationEffect implements Parcelable { PRIMITIVE_LOW_TICK, }) @Retention(RetentionPolicy.SOURCE) - public @interface Primitive {} + public @interface PrimitiveType {} /** * No haptic effect. Used to generate extended delays between primitives. @@ -1166,9 +765,10 @@ public abstract class VibrationEffect implements Parcelable { public static final int PRIMITIVE_LOW_TICK = 8; - private ArrayList mEffects = new ArrayList<>(); + private final ArrayList mSegments = new ArrayList<>(); + private int mRepeatIndex = -1; - Composition() { } + Composition() {} /** * Add a haptic primitive to the end of the current composition. @@ -1181,9 +781,8 @@ public abstract class VibrationEffect implements Parcelable { * @return The {@link Composition} object to enable adding multiple primitives in one chain. */ @NonNull - public Composition addPrimitive(@Primitive int primitiveId) { - addPrimitive(primitiveId, /*scale*/ 1.0f, /*delay*/ 0); - return this; + public Composition addPrimitive(@PrimitiveType int primitiveId) { + return addPrimitive(primitiveId, /*scale*/ 1.0f, /*delay*/ 0); } /** @@ -1197,10 +796,9 @@ public abstract class VibrationEffect implements Parcelable { * @return The {@link Composition} object to enable adding multiple primitives in one chain. */ @NonNull - public Composition addPrimitive(@Primitive int primitiveId, + public Composition addPrimitive(@PrimitiveType int primitiveId, @FloatRange(from = 0f, to = 1f) float scale) { - addPrimitive(primitiveId, scale, /*delay*/ 0); - return this; + return addPrimitive(primitiveId, scale, /*delay*/ 0); } /** @@ -1213,9 +811,21 @@ public abstract class VibrationEffect implements Parcelable { * @return The {@link Composition} object to enable adding multiple primitives in one chain. */ @NonNull - public Composition addPrimitive(@Primitive int primitiveId, + public Composition addPrimitive(@PrimitiveType int primitiveId, @FloatRange(from = 0f, to = 1f) float scale, @IntRange(from = 0) int delay) { - mEffects.add(new PrimitiveEffect(checkPrimitive(primitiveId), scale, delay)); + PrimitiveSegment primitive = new PrimitiveSegment(primitiveId, scale, + delay); + primitive.validate(); + return addSegment(primitive); + } + + private Composition addSegment(VibrationEffectSegment segment) { + if (mRepeatIndex >= 0) { + throw new IllegalStateException( + "Composition already have a repeating effect so any new primitive would be" + + " unreachable."); + } + mSegments.add(segment); return this; } @@ -1230,22 +840,13 @@ public abstract class VibrationEffect implements Parcelable { */ @NonNull public VibrationEffect compose() { - if (mEffects.isEmpty()) { + if (mSegments.isEmpty()) { throw new IllegalStateException( "Composition must have at least one element to compose."); } - return new VibrationEffect.Composed(mEffects); - } - - /** - * @throws IllegalArgumentException throws if the primitive ID is not within the valid range - * @hide - * - */ - static int checkPrimitive(int primitiveId) { - Preconditions.checkArgumentInRange(primitiveId, PRIMITIVE_NOOP, PRIMITIVE_LOW_TICK, - "primitiveId"); - return primitiveId; + VibrationEffect effect = new Composed(mSegments, mRepeatIndex); + effect.validate(); + return effect; } /** @@ -1254,7 +855,7 @@ public abstract class VibrationEffect implements Parcelable { * @return The ID in a human readable format. * @hide */ - public static String primitiveToString(@Primitive int id) { + public static String primitiveToString(@PrimitiveType int id) { switch (id) { case PRIMITIVE_NOOP: return "PRIMITIVE_NOOP"; @@ -1278,90 +879,14 @@ public abstract class VibrationEffect implements Parcelable { return Integer.toString(id); } } - - - /** - * @hide - */ - public static class PrimitiveEffect implements Parcelable { - public int id; - public float scale; - public int delay; - - PrimitiveEffect(int id, float scale, int delay) { - this.id = id; - this.scale = scale; - this.delay = delay; - } - - @Override - public void writeToParcel(Parcel dest, int flags) { - dest.writeInt(id); - dest.writeFloat(scale); - dest.writeInt(delay); - } - - @Override - public int describeContents() { - return 0; - } - - @Override - public String toString() { - return "PrimitiveEffect{" - + "id=" + primitiveToString(id) - + ", scale=" + scale - + ", delay=" + delay - + '}'; - } - - @Override - public boolean equals(@Nullable Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - PrimitiveEffect that = (PrimitiveEffect) o; - return id == that.id - && Float.compare(that.scale, scale) == 0 - && delay == that.delay; - } - - @Override - public int hashCode() { - return Objects.hash(id, scale, delay); - } - - - public static final @NonNull Parcelable.Creator CREATOR = - new Parcelable.Creator() { - @Override - public PrimitiveEffect createFromParcel(Parcel in) { - return new PrimitiveEffect(in.readInt(), in.readFloat(), in.readInt()); - } - @Override - public PrimitiveEffect[] newArray(int size) { - return new PrimitiveEffect[size]; - } - }; - } } - public static final @NonNull Parcelable.Creator CREATOR = + @NonNull + public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { @Override public VibrationEffect createFromParcel(Parcel in) { - int token = in.readInt(); - if (token == PARCEL_TOKEN_ONE_SHOT) { - return new OneShot(in); - } else if (token == PARCEL_TOKEN_WAVEFORM) { - return new Waveform(in); - } else if (token == PARCEL_TOKEN_EFFECT) { - return new Prebaked(in); - } else if (token == PARCEL_TOKEN_COMPOSITION) { - return new Composed(in); - } else { - throw new IllegalStateException( - "Unexpected vibration event type token in parcel."); - } + return new Composed(in); } @Override public VibrationEffect[] newArray(int size) { diff --git a/core/java/android/os/Vibrator.java b/core/java/android/os/Vibrator.java index b90d438ffb935..a0f70c8fa5260 100644 --- a/core/java/android/os/Vibrator.java +++ b/core/java/android/os/Vibrator.java @@ -467,7 +467,7 @@ public abstract class Vibrator { */ @NonNull public boolean[] arePrimitivesSupported( - @NonNull @VibrationEffect.Composition.Primitive int... primitiveIds) { + @NonNull @VibrationEffect.Composition.PrimitiveType int... primitiveIds) { return new boolean[primitiveIds.length]; } @@ -478,7 +478,7 @@ public abstract class Vibrator { * @return Whether primitives effects are supported. */ public final boolean areAllPrimitivesSupported( - @NonNull @VibrationEffect.Composition.Primitive int... primitiveIds) { + @NonNull @VibrationEffect.Composition.PrimitiveType int... primitiveIds) { for (boolean supported : arePrimitivesSupported(primitiveIds)) { if (!supported) { return false; diff --git a/core/java/android/os/VibratorInfo.java b/core/java/android/os/VibratorInfo.java index 3121b952281e4..9c46bc9eb1497 100644 --- a/core/java/android/os/VibratorInfo.java +++ b/core/java/android/os/VibratorInfo.java @@ -153,7 +153,8 @@ public final class VibratorInfo implements Parcelable { * @param primitiveId Which primitives to query for. * @return Whether the primitive is supported. */ - public boolean isPrimitiveSupported(@VibrationEffect.Composition.Primitive int primitiveId) { + public boolean isPrimitiveSupported( + @VibrationEffect.Composition.PrimitiveType int primitiveId) { return hasCapability(IVibrator.CAP_COMPOSE_EFFECTS) && mSupportedPrimitives != null && mSupportedPrimitives.get(primitiveId, false); } diff --git a/core/java/android/os/vibrator/PrebakedSegment.java b/core/java/android/os/vibrator/PrebakedSegment.java new file mode 100644 index 0000000000000..78b43468d663f --- /dev/null +++ b/core/java/android/os/vibrator/PrebakedSegment.java @@ -0,0 +1,184 @@ +/* + * Copyright (C) 2021 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 android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.TestApi; +import android.os.Parcel; +import android.os.Parcelable; +import android.os.VibrationEffect; + +import java.util.Objects; + +/** + * Representation of {@link VibrationEffectSegment} that plays a prebaked vibration effect. + * + * @hide + */ +@TestApi +public final class PrebakedSegment extends VibrationEffectSegment { + private final int mEffectId; + private final boolean mFallback; + private final int mEffectStrength; + + PrebakedSegment(@NonNull Parcel in) { + mEffectId = in.readInt(); + mFallback = in.readByte() != 0; + mEffectStrength = in.readInt(); + } + + /** @hide */ + public PrebakedSegment(int effectId, boolean shouldFallback, int effectStrength) { + mEffectId = effectId; + mFallback = shouldFallback; + mEffectStrength = effectStrength; + } + + public int getEffectId() { + return mEffectId; + } + + public int getEffectStrength() { + return mEffectStrength; + } + + /** Return true if a fallback effect should be played if this effect is not supported. */ + public boolean shouldFallback() { + return mFallback; + } + + @Override + public long getDuration() { + return -1; + } + + @Override + public boolean hasNonZeroAmplitude() { + return true; + } + + @NonNull + @Override + public PrebakedSegment resolve(int defaultAmplitude) { + return this; + } + + @NonNull + @Override + public PrebakedSegment scale(float scaleFactor) { + // Prebaked effect strength cannot be scaled with this method. + return this; + } + + @NonNull + @Override + public PrebakedSegment applyEffectStrength(int effectStrength) { + if (effectStrength != mEffectStrength && isValidEffectStrength(effectStrength)) { + return new PrebakedSegment(mEffectId, mFallback, effectStrength); + } + return this; + } + + private static boolean isValidEffectStrength(int strength) { + switch (strength) { + case VibrationEffect.EFFECT_STRENGTH_LIGHT: + case VibrationEffect.EFFECT_STRENGTH_MEDIUM: + case VibrationEffect.EFFECT_STRENGTH_STRONG: + return true; + default: + return false; + } + } + + @Override + public void validate() { + switch (mEffectId) { + case VibrationEffect.EFFECT_CLICK: + case VibrationEffect.EFFECT_DOUBLE_CLICK: + case VibrationEffect.EFFECT_TICK: + case VibrationEffect.EFFECT_TEXTURE_TICK: + case VibrationEffect.EFFECT_THUD: + case VibrationEffect.EFFECT_POP: + case VibrationEffect.EFFECT_HEAVY_CLICK: + break; + default: + int[] ringtones = VibrationEffect.RINGTONES; + if (mEffectId < ringtones[0] || mEffectId > ringtones[ringtones.length - 1]) { + throw new IllegalArgumentException( + "Unknown prebaked effect type (value=" + mEffectId + ")"); + } + } + if (!isValidEffectStrength(mEffectStrength)) { + throw new IllegalArgumentException( + "Unknown prebaked effect strength (value=" + mEffectStrength + ")"); + } + } + + @Override + public boolean equals(@Nullable Object o) { + if (!(o instanceof PrebakedSegment)) { + return false; + } + PrebakedSegment other = (PrebakedSegment) o; + return mEffectId == other.mEffectId + && mFallback == other.mFallback + && mEffectStrength == other.mEffectStrength; + } + + @Override + public int hashCode() { + return Objects.hash(mEffectId, mFallback, mEffectStrength); + } + + @Override + public String toString() { + return "Prebaked{effect=" + VibrationEffect.effectIdToString(mEffectId) + + ", strength=" + VibrationEffect.effectStrengthToString(mEffectStrength) + + ", fallback=" + mFallback + + "}"; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel out, int flags) { + out.writeInt(PARCEL_TOKEN_PREBAKED); + out.writeInt(mEffectId); + out.writeByte((byte) (mFallback ? 1 : 0)); + out.writeInt(mEffectStrength); + } + + @NonNull + public static final Parcelable.Creator CREATOR = + new Parcelable.Creator() { + @Override + public PrebakedSegment createFromParcel(Parcel in) { + // Skip the type token + in.readInt(); + return new PrebakedSegment(in); + } + + @Override + public PrebakedSegment[] newArray(int size) { + return new PrebakedSegment[size]; + } + }; +} diff --git a/core/java/android/os/vibrator/PrimitiveSegment.java b/core/java/android/os/vibrator/PrimitiveSegment.java new file mode 100644 index 0000000000000..2ef29cb26ebc0 --- /dev/null +++ b/core/java/android/os/vibrator/PrimitiveSegment.java @@ -0,0 +1,155 @@ +/* + * Copyright (C) 2021 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 android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.TestApi; +import android.os.Parcel; +import android.os.Parcelable; +import android.os.VibrationEffect; + +import com.android.internal.util.Preconditions; + +import java.util.Objects; + +/** + * Representation of {@link VibrationEffectSegment} that plays a primitive vibration effect after a + * specified delay and applying a given scale. + * + * @hide + */ +@TestApi +public final class PrimitiveSegment extends VibrationEffectSegment { + private final int mPrimitiveId; + private final float mScale; + private final int mDelay; + + PrimitiveSegment(@NonNull Parcel in) { + this(in.readInt(), in.readFloat(), in.readInt()); + } + + /** @hide */ + public PrimitiveSegment(int id, float scale, int delay) { + mPrimitiveId = id; + mScale = scale; + mDelay = delay; + } + + public int getPrimitiveId() { + return mPrimitiveId; + } + + public float getScale() { + return mScale; + } + + public int getDelay() { + return mDelay; + } + + @Override + public long getDuration() { + return -1; + } + + @Override + public boolean hasNonZeroAmplitude() { + // Every primitive plays a vibration with a non-zero amplitude, even at scale == 0. + return true; + } + + @NonNull + @Override + public PrimitiveSegment resolve(int defaultAmplitude) { + return this; + } + + @NonNull + @Override + public PrimitiveSegment scale(float scaleFactor) { + return new PrimitiveSegment(mPrimitiveId, VibrationEffect.scale(mScale, scaleFactor), + mDelay); + } + + @NonNull + @Override + public PrimitiveSegment applyEffectStrength(int effectStrength) { + return this; + } + + @Override + public void validate() { + Preconditions.checkArgumentInRange(mPrimitiveId, VibrationEffect.Composition.PRIMITIVE_NOOP, + VibrationEffect.Composition.PRIMITIVE_LOW_TICK, "primitiveId"); + Preconditions.checkArgumentInRange(mScale, 0f, 1f, "scale"); + Preconditions.checkArgumentNonnegative(mDelay, "primitive delay should be >= 0"); + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeInt(PARCEL_TOKEN_PRIMITIVE); + dest.writeInt(mPrimitiveId); + dest.writeFloat(mScale); + dest.writeInt(mDelay); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public String toString() { + return "Primitive{" + + "primitive=" + VibrationEffect.Composition.primitiveToString(mPrimitiveId) + + ", scale=" + mScale + + ", delay=" + mDelay + + '}'; + } + + @Override + public boolean equals(@Nullable Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + PrimitiveSegment that = (PrimitiveSegment) o; + return mPrimitiveId == that.mPrimitiveId + && Float.compare(that.mScale, mScale) == 0 + && mDelay == that.mDelay; + } + + @Override + public int hashCode() { + return Objects.hash(mPrimitiveId, mScale, mDelay); + } + + @NonNull + public static final Parcelable.Creator CREATOR = + new Parcelable.Creator() { + @Override + public PrimitiveSegment createFromParcel(Parcel in) { + // Skip the type token + in.readInt(); + return new PrimitiveSegment(in); + } + + @Override + public PrimitiveSegment[] newArray(int size) { + return new PrimitiveSegment[size]; + } + }; +} diff --git a/core/java/android/os/vibrator/StepSegment.java b/core/java/android/os/vibrator/StepSegment.java new file mode 100644 index 0000000000000..61a5d6cec9b13 --- /dev/null +++ b/core/java/android/os/vibrator/StepSegment.java @@ -0,0 +1,152 @@ +/* + * Copyright (C) 2021 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 android.annotation.NonNull; +import android.annotation.TestApi; +import android.os.Parcel; +import android.os.Parcelable; +import android.os.VibrationEffect; + +import com.android.internal.util.Preconditions; + +import java.util.Objects; + +/** + * Representation of {@link VibrationEffectSegment} that holds a fixed vibration amplitude for a + * specified duration. + * + * @hide + */ +@TestApi +public final class StepSegment extends VibrationEffectSegment { + private final float mAmplitude; + private final int mDuration; + + StepSegment(@NonNull Parcel in) { + this(in.readFloat(), in.readInt()); + } + + /** @hide */ + public StepSegment(float amplitude, int duration) { + mAmplitude = amplitude; + mDuration = duration; + } + + @Override + public boolean equals(Object o) { + if (!(o instanceof StepSegment)) { + return false; + } + StepSegment other = (StepSegment) o; + return Float.compare(mAmplitude, other.mAmplitude) == 0 + && mDuration == other.mDuration; + } + + public float getAmplitude() { + return mAmplitude; + } + + @Override + public long getDuration() { + return mDuration; + } + + @Override + public boolean hasNonZeroAmplitude() { + // DEFAULT_AMPLITUDE == -1 is still a non-zero amplitude that will be resolved later. + return Float.compare(mAmplitude, 0) != 0; + } + + @Override + public void validate() { + Preconditions.checkArgumentNonnegative(mDuration, + "Durations must all be >= 0, got " + mDuration); + if (Float.compare(mAmplitude, VibrationEffect.DEFAULT_AMPLITUDE) != 0) { + Preconditions.checkArgumentInRange(mAmplitude, 0f, 1f, "amplitude"); + } + } + + @NonNull + @Override + public StepSegment resolve(int defaultAmplitude) { + if (defaultAmplitude > VibrationEffect.MAX_AMPLITUDE || defaultAmplitude <= 0) { + throw new IllegalArgumentException( + "amplitude must be between 1 and 255 inclusive (amplitude=" + + defaultAmplitude + ")"); + } + if (Float.compare(mAmplitude, VibrationEffect.DEFAULT_AMPLITUDE) != 0) { + return this; + } + return new StepSegment((float) defaultAmplitude / VibrationEffect.MAX_AMPLITUDE, mDuration); + } + + @NonNull + @Override + public StepSegment scale(float scaleFactor) { + if (Float.compare(mAmplitude, VibrationEffect.DEFAULT_AMPLITUDE) == 0) { + return this; + } + return new StepSegment(VibrationEffect.scale(mAmplitude, scaleFactor), mDuration); + } + + @NonNull + @Override + public StepSegment applyEffectStrength(int effectStrength) { + return this; + } + + @Override + public int hashCode() { + return Objects.hash(mAmplitude, mDuration); + } + + @Override + public String toString() { + return "Step{amplitude=" + mAmplitude + + ", duration=" + mDuration + + "}"; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel out, int flags) { + out.writeInt(PARCEL_TOKEN_STEP); + out.writeFloat(mAmplitude); + out.writeInt(mDuration); + } + + @NonNull + public static final Parcelable.Creator CREATOR = + new Parcelable.Creator() { + @Override + public StepSegment createFromParcel(Parcel in) { + // Skip the type token + in.readInt(); + return new StepSegment(in); + } + + @Override + public StepSegment[] newArray(int size) { + return new StepSegment[size]; + } + }; +} diff --git a/core/java/android/os/vibrator/VibrationEffectSegment.java b/core/java/android/os/vibrator/VibrationEffectSegment.java new file mode 100644 index 0000000000000..3dc9e1252ecc6 --- /dev/null +++ b/core/java/android/os/vibrator/VibrationEffectSegment.java @@ -0,0 +1,114 @@ +/* + * Copyright (C) 2021 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 android.annotation.NonNull; +import android.annotation.TestApi; +import android.os.Parcel; +import android.os.Parcelable; +import android.os.VibrationEffect; + +/** + * Representation of a single segment of a {@link VibrationEffect}. + * + *

Vibration effects are represented as a sequence of segments that describes how vibration + * amplitude and frequency changes over time. Segments can be described as one of the following: + * + *

    + *
  1. A predefined vibration effect; + *
  2. A composable effect primitive; + *
  3. Fixed amplitude value to be held for a specified duration; + *
+ * + * @hide + */ +@TestApi +@SuppressWarnings({"ParcelNotFinal", "ParcelCreator"}) // Parcel only extended here. +public abstract class VibrationEffectSegment implements Parcelable { + static final int PARCEL_TOKEN_PREBAKED = 1; + static final int PARCEL_TOKEN_PRIMITIVE = 2; + static final int PARCEL_TOKEN_STEP = 3; + + /** Prevent subclassing from outside of this package */ + VibrationEffectSegment() { + } + + /** + * Gets the estimated duration of the segment in milliseconds. + * + *

For segments with an unknown duration (e.g. prebaked or primitive effects where the length + * is device and potentially run-time dependent), this returns -1. + */ + public abstract long getDuration(); + + /** Returns true if this segment plays at a non-zero amplitude at some point. */ + public abstract boolean hasNonZeroAmplitude(); + + /** Validates the segment, throwing exceptions if any parameter is invalid. */ + public abstract void validate(); + + /** + * Resolves amplitudes set to {@link VibrationEffect#DEFAULT_AMPLITUDE}. + * + *

This might fail with {@link IllegalArgumentException} if value is non-positive or larger + * than {@link VibrationEffect#MAX_AMPLITUDE}. + */ + @NonNull + public abstract T resolve(int defaultAmplitude); + + /** + * Scale 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 + */ + @NonNull + public abstract T scale(float scaleFactor); + + /** + * Applies given effect strength to prebaked effects. + * + * @param effectStrength new effect strength to be applied, one of + * VibrationEffect.EFFECT_STRENGTH_*. + */ + @NonNull + public abstract T applyEffectStrength(int effectStrength); + + @NonNull + public static final Creator CREATOR = + new Creator() { + @Override + public VibrationEffectSegment createFromParcel(Parcel in) { + switch (in.readInt()) { + case PARCEL_TOKEN_STEP: + return new StepSegment(in); + case PARCEL_TOKEN_PREBAKED: + return new PrebakedSegment(in); + case PARCEL_TOKEN_PRIMITIVE: + return new PrimitiveSegment(in); + default: + throw new IllegalStateException( + "Unexpected vibration event type token in parcel."); + } + } + + @Override + public VibrationEffectSegment[] newArray(int size) { + return new VibrationEffectSegment[size]; + } + }; +} diff --git a/core/proto/android/server/vibrator/vibratormanagerservice.proto b/core/proto/android/server/vibrator/vibratormanagerservice.proto index aab054f4bf738..16c3ab098832b 100644 --- a/core/proto/android/server/vibrator/vibratormanagerservice.proto +++ b/core/proto/android/server/vibrator/vibratormanagerservice.proto @@ -21,40 +21,38 @@ option java_multiple_files = true; import "frameworks/base/core/proto/android/privacy.proto"; -message OneShotProto { - option (.android.msg_privacy).dest = DEST_AUTOMATIC; - repeated int32 duration = 1; - repeated int32 amplitude = 2; -} - -message WaveformProto { +message StepSegmentProto { option (.android.msg_privacy).dest = DEST_AUTOMATIC; - repeated int32 timings = 1; - repeated int32 amplitudes = 2; - required bool repeat = 3; + optional int32 duration = 1; + optional float amplitude = 2; } -message PrebakedProto { +message PrebakedSegmentProto { option (.android.msg_privacy).dest = DEST_AUTOMATIC; optional int32 effect_id = 1; optional int32 effect_strength = 2; optional int32 fallback = 3; } -message ComposedProto { +message PrimitiveSegmentProto { option (.android.msg_privacy).dest = DEST_AUTOMATIC; - repeated int32 effect_ids = 1; - repeated float effect_scales = 2; - repeated int32 delays = 3; + optional int32 primitive_id = 1; + optional float scale = 2; + optional int32 delay = 3; +} + +message SegmentProto { + option (.android.msg_privacy).dest = DEST_AUTOMATIC; + optional PrebakedSegmentProto prebaked = 1; + optional PrimitiveSegmentProto primitive = 2; + optional StepSegmentProto step = 3; } // A com.android.os.VibrationEffect object. message VibrationEffectProto { option (.android.msg_privacy).dest = DEST_AUTOMATIC; - optional OneShotProto oneshot = 1; - optional WaveformProto waveform = 2; - optional PrebakedProto prebaked = 3; - optional ComposedProto composed = 4; + optional SegmentProto segments = 1; + required int32 repeat = 2; } message SyncVibrationEffectProto { diff --git a/core/tests/coretests/src/android/os/CombinedVibrationEffectTest.java b/core/tests/coretests/src/android/os/CombinedVibrationEffectTest.java index 11239db9f4041..30b2d8e47ab1e 100644 --- a/core/tests/coretests/src/android/os/CombinedVibrationEffectTest.java +++ b/core/tests/coretests/src/android/os/CombinedVibrationEffectTest.java @@ -28,13 +28,15 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; +import java.util.ArrayList; import java.util.Arrays; @Presubmit @RunWith(JUnit4.class) public class CombinedVibrationEffectTest { private static final VibrationEffect VALID_EFFECT = VibrationEffect.createOneShot(10, 255); - private static final VibrationEffect INVALID_EFFECT = new VibrationEffect.OneShot(-1, -1); + private static final VibrationEffect INVALID_EFFECT = new VibrationEffect.Composed( + new ArrayList<>(), 0); @Test public void testValidateMono() { diff --git a/core/tests/coretests/src/android/os/VibrationEffectTest.java b/core/tests/coretests/src/android/os/VibrationEffectTest.java index d555cd90d907d..242adab140a3a 100644 --- a/core/tests/coretests/src/android/os/VibrationEffectTest.java +++ b/core/tests/coretests/src/android/os/VibrationEffectTest.java @@ -18,13 +18,9 @@ package android.os; import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.assertNotNull; -import static junit.framework.Assert.assertNotSame; import static junit.framework.Assert.assertNull; -import static junit.framework.Assert.assertSame; import static junit.framework.Assert.assertTrue; -import static junit.framework.Assert.fail; -import static org.junit.Assert.assertArrayEquals; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -35,11 +31,13 @@ import android.content.ContentResolver; import android.content.Context; import android.content.res.Resources; import android.net.Uri; +import android.os.vibrator.PrebakedSegment; +import android.os.vibrator.PrimitiveSegment; +import android.os.vibrator.StepSegment; import android.platform.test.annotations.Presubmit; import com.android.internal.R; -import org.junit.Assert; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.junit.MockitoJUnitRunner; @@ -53,9 +51,6 @@ public class VibrationEffectTest { private static final String RINGTONE_URI_3 = "content://test/system/ringtone_3"; private static final String UNKNOWN_URI = "content://test/system/other_audio"; - private static final float INTENSITY_SCALE_TOLERANCE = 1e-2f; - private static final int AMPLITUDE_SCALE_TOLERANCE = 1; - private static final long TEST_TIMING = 100; private static final int TEST_AMPLITUDE = 100; private static final long[] TEST_TIMINGS = new long[] { 100, 100, 200 }; @@ -68,12 +63,6 @@ public class VibrationEffectTest { VibrationEffect.createOneShot(TEST_TIMING, VibrationEffect.DEFAULT_AMPLITUDE); private static final VibrationEffect TEST_WAVEFORM = VibrationEffect.createWaveform(TEST_TIMINGS, TEST_AMPLITUDES, -1); - private static final VibrationEffect TEST_COMPOSED = - VibrationEffect.startComposition() - .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1f, 1) - .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 0.5f, 10) - .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 0f, 100) - .compose(); @Test public void getRingtones_noPrebakedRingtones() { @@ -168,251 +157,107 @@ public class VibrationEffectTest { .validate()); } - @Test - public void testScalePrebaked_scalesFallbackEffect() { - VibrationEffect.Prebaked prebaked = - (VibrationEffect.Prebaked) VibrationEffect.get(VibrationEffect.RINGTONES[1]); - assertSame(prebaked, prebaked.scale(0.5f)); - - prebaked = new VibrationEffect.Prebaked(VibrationEffect.EFFECT_CLICK, - VibrationEffect.EFFECT_STRENGTH_MEDIUM, TEST_ONE_SHOT); - VibrationEffect.OneShot scaledFallback = - (VibrationEffect.OneShot) prebaked.scale(0.5f).getFallbackEffect(); - assertEquals(34, scaledFallback.getAmplitude(), AMPLITUDE_SCALE_TOLERANCE); - } - - @Test - public void testResolvePrebaked_resolvesFallbackEffectIfSet() { - VibrationEffect.Prebaked prebaked = - (VibrationEffect.Prebaked) VibrationEffect.get(VibrationEffect.RINGTONES[1]); - assertSame(prebaked, prebaked.resolve(1000)); - - prebaked = new VibrationEffect.Prebaked(VibrationEffect.EFFECT_CLICK, - VibrationEffect.EFFECT_STRENGTH_MEDIUM, - VibrationEffect.createOneShot(1, VibrationEffect.DEFAULT_AMPLITUDE)); - VibrationEffect.OneShot resolvedFallback = - (VibrationEffect.OneShot) prebaked.resolve(10).getFallbackEffect(); - assertEquals(10, resolvedFallback.getAmplitude()); - } - - @Test - public void testScaleOneShot() { - VibrationEffect.OneShot unset = new VibrationEffect.OneShot( - TEST_TIMING, VibrationEffect.DEFAULT_AMPLITUDE); - assertEquals(VibrationEffect.DEFAULT_AMPLITUDE, unset.scale(2).getAmplitude()); - - VibrationEffect.OneShot initial = (VibrationEffect.OneShot) TEST_ONE_SHOT; - - VibrationEffect.OneShot halved = initial.scale(0.5f); - assertEquals(34, halved.getAmplitude(), AMPLITUDE_SCALE_TOLERANCE); - - VibrationEffect.OneShot copied = initial.scale(1f); - assertEquals(TEST_AMPLITUDE, copied.getAmplitude()); - - VibrationEffect.OneShot scaledUp = initial.scale(1.5f); - assertTrue(scaledUp.getAmplitude() > initial.getAmplitude()); - VibrationEffect.OneShot restored = scaledUp.scale(2 / 3f); - // Does not restore to the exact original value because scale up is a bit offset. - assertEquals(105, restored.getAmplitude(), AMPLITUDE_SCALE_TOLERANCE); - - VibrationEffect.OneShot scaledDown = initial.scale(0.8f); - assertTrue(scaledDown.getAmplitude() < initial.getAmplitude()); - restored = scaledDown.scale(1.25f); - // Does not restore to the exact original value because scale up is a bit offset. - assertEquals(101, restored.getAmplitude(), AMPLITUDE_SCALE_TOLERANCE); - - // Does not go below min amplitude while scaling down. - VibrationEffect.OneShot minAmplitude = new VibrationEffect.OneShot(TEST_TIMING, 1); - assertEquals(1, minAmplitude.scale(0.5f).getAmplitude()); - } - @Test public void testResolveOneShot() { - VibrationEffect.OneShot initial = (VibrationEffect.OneShot) DEFAULT_ONE_SHOT; - VibrationEffect.OneShot resolved = initial.resolve(239); - assertNotSame(initial, resolved); - assertEquals(239, resolved.getAmplitude()); + VibrationEffect.Composed resolved = DEFAULT_ONE_SHOT.resolve(51); + assertEquals(0.2f, ((StepSegment) resolved.getSegments().get(0)).getAmplitude()); - // Ignores input when amplitude already set. - VibrationEffect.OneShot resolved2 = resolved.resolve(10); - assertSame(resolved, resolved2); - assertEquals(239, resolved2.getAmplitude()); - } - - @Test - public void testResolveOneshotFailsWhenMaxAmplitudeAboveThreshold() { - try { - TEST_ONE_SHOT.resolve(1000); - fail("Max amplitude above threshold, should throw IllegalArgumentException"); - } catch (IllegalArgumentException expected) { - } - } - - @Test - public void testResolveOneshotFailsWhenAmplitudeNonPositive() { - try { - TEST_ONE_SHOT.resolve(0); - fail("Amplitude is set to zero, should throw IllegalArgumentException"); - } catch (IllegalArgumentException expected) { - } - } - - @Test - public void testScaleWaveform() { - VibrationEffect.Waveform initial = (VibrationEffect.Waveform) TEST_WAVEFORM; - - VibrationEffect.Waveform copied = initial.scale(1f); - assertArrayEquals(TEST_AMPLITUDES, copied.getAmplitudes()); - - VibrationEffect.Waveform scaled = initial.scale(0.9f); - assertEquals(216, scaled.getAmplitudes()[0], AMPLITUDE_SCALE_TOLERANCE); - assertEquals(0, scaled.getAmplitudes()[1]); - assertEquals(-1, scaled.getAmplitudes()[2]); - - VibrationEffect.Waveform minAmplitude = new VibrationEffect.Waveform( - new long[]{100}, new int[] {1}, -1); - assertArrayEquals(new int[]{1}, minAmplitude.scale(0.5f).getAmplitudes()); + assertThrows(IllegalArgumentException.class, () -> DEFAULT_ONE_SHOT.resolve(1000)); } @Test public void testResolveWaveform() { - VibrationEffect.Waveform initial = (VibrationEffect.Waveform) TEST_WAVEFORM; - VibrationEffect.Waveform resolved = initial.resolve(123); - assertNotSame(initial, resolved); - assertArrayEquals(new int[]{255, 0, 123}, resolved.getAmplitudes()); + VibrationEffect.Composed resolved = TEST_WAVEFORM.resolve(102); + assertEquals(0.4f, ((StepSegment) resolved.getSegments().get(2)).getAmplitude()); - // Ignores input when amplitude already set. - VibrationEffect.Waveform resolved2 = resolved.resolve(10); - assertSame(resolved, resolved2); - assertArrayEquals(new int[]{255, 0, 123}, resolved2.getAmplitudes()); + assertThrows(IllegalArgumentException.class, () -> TEST_WAVEFORM.resolve(1000)); } @Test - public void testResolveWaveformFailsWhenMaxAmplitudeAboveThreshold() { - try { - TEST_WAVEFORM.resolve(1000); - fail("Max amplitude above threshold, should throw IllegalArgumentException"); - } catch (IllegalArgumentException expected) { - } + public void testResolvePrebaked() { + VibrationEffect effect = VibrationEffect.get(VibrationEffect.EFFECT_CLICK); + assertEquals(effect, effect.resolve(51)); + } + + @Test + public void testResolveComposed() { + VibrationEffect effect = VibrationEffect.startComposition() + .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1f, 1) + .compose(); + assertEquals(effect, effect.resolve(51)); + } + + @Test + public void testApplyEffectStrengthOneShot() { + VibrationEffect.Composed applied = DEFAULT_ONE_SHOT.applyEffectStrength( + VibrationEffect.EFFECT_STRENGTH_LIGHT); + assertEquals(DEFAULT_ONE_SHOT, applied); + } + + @Test + public void testApplyEffectStrengthWaveform() { + VibrationEffect.Composed applied = TEST_WAVEFORM.applyEffectStrength( + VibrationEffect.EFFECT_STRENGTH_LIGHT); + assertEquals(TEST_WAVEFORM, applied); + } + + @Test + public void testApplyEffectStrengthPrebaked() { + VibrationEffect.Composed applied = VibrationEffect.get(VibrationEffect.EFFECT_CLICK) + .applyEffectStrength(VibrationEffect.EFFECT_STRENGTH_LIGHT); + assertEquals(VibrationEffect.EFFECT_STRENGTH_LIGHT, + ((PrebakedSegment) applied.getSegments().get(0)).getEffectStrength()); + } + + @Test + public void testApplyEffectStrengthComposed() { + VibrationEffect effect = VibrationEffect.startComposition() + .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 0.5f, 1) + .compose(); + assertEquals(effect, effect.applyEffectStrength(VibrationEffect.EFFECT_STRENGTH_LIGHT)); + } + + @Test + public void testScaleOneShot() { + VibrationEffect.Composed scaledUp = TEST_ONE_SHOT.scale(1.5f); + assertTrue(100 / 255f < ((StepSegment) scaledUp.getSegments().get(0)).getAmplitude()); + + VibrationEffect.Composed scaledDown = TEST_ONE_SHOT.scale(0.5f); + assertTrue(100 / 255f > ((StepSegment) scaledDown.getSegments().get(0)).getAmplitude()); + } + + @Test + public void testScaleWaveform() { + VibrationEffect.Composed scaledUp = TEST_WAVEFORM.scale(1.5f); + assertEquals(1f, ((StepSegment) scaledUp.getSegments().get(0)).getAmplitude(), 1e-5f); + + VibrationEffect.Composed scaledDown = TEST_WAVEFORM.scale(0.5f); + assertTrue(1f > ((StepSegment) scaledDown.getSegments().get(0)).getAmplitude()); + } + + @Test + public void testScalePrebaked() { + VibrationEffect effect = VibrationEffect.get(VibrationEffect.EFFECT_CLICK); + + VibrationEffect.Composed scaledUp = effect.scale(1.5f); + assertEquals(effect, scaledUp); + + VibrationEffect.Composed scaledDown = effect.scale(0.5f); + assertEquals(effect, scaledDown); } @Test public void testScaleComposed() { - VibrationEffect.Composed initial = (VibrationEffect.Composed) TEST_COMPOSED; - - VibrationEffect.Composed copied = initial.scale(1); - assertEquals(1f, copied.getPrimitiveEffects().get(0).scale); - assertEquals(0.5f, copied.getPrimitiveEffects().get(1).scale); - assertEquals(0f, copied.getPrimitiveEffects().get(2).scale); - - VibrationEffect.Composed halved = initial.scale(0.5f); - assertEquals(0.34f, halved.getPrimitiveEffects().get(0).scale, INTENSITY_SCALE_TOLERANCE); - assertEquals(0.17f, halved.getPrimitiveEffects().get(1).scale, INTENSITY_SCALE_TOLERANCE); - assertEquals(0f, halved.getPrimitiveEffects().get(2).scale); - - VibrationEffect.Composed scaledUp = initial.scale(1.5f); - // Does not scale up from 1. - assertEquals(1f, scaledUp.getPrimitiveEffects().get(0).scale, INTENSITY_SCALE_TOLERANCE); - assertTrue(0.5f < scaledUp.getPrimitiveEffects().get(1).scale); - assertEquals(0f, scaledUp.getPrimitiveEffects().get(2).scale); - - VibrationEffect.Composed restored = scaledUp.scale(2 / 3f); - // The original value was not scaled up, so this only scales it down. - assertEquals(0.53f, restored.getPrimitiveEffects().get(0).scale, INTENSITY_SCALE_TOLERANCE); - // Does not restore to the exact original value because scale up is a bit offset. - assertEquals(0.47f, restored.getPrimitiveEffects().get(1).scale, INTENSITY_SCALE_TOLERANCE); - assertEquals(0f, restored.getPrimitiveEffects().get(2).scale); - - VibrationEffect.Composed scaledDown = initial.scale(0.8f); - assertTrue(1f > scaledDown.getPrimitiveEffects().get(0).scale); - assertTrue(0.5f > scaledDown.getPrimitiveEffects().get(1).scale); - assertEquals(0f, scaledDown.getPrimitiveEffects().get(2).scale); - - restored = scaledDown.scale(1.25f); - // Does not restore to the exact original value because scale up is a bit offset. - assertEquals(0.84f, restored.getPrimitiveEffects().get(0).scale, INTENSITY_SCALE_TOLERANCE); - assertEquals(0.5f, restored.getPrimitiveEffects().get(1).scale, INTENSITY_SCALE_TOLERANCE); - assertEquals(0f, restored.getPrimitiveEffects().get(2).scale); - } - - @Test - public void testResolveComposed_ignoresDefaultAmplitudeAndReturnsSameEffect() { - VibrationEffect initial = TEST_COMPOSED; - assertSame(initial, initial.resolve(1000)); - } - - @Test - public void testScaleAppliesSameAdjustmentsOnAllEffects() { - VibrationEffect.OneShot oneShot = new VibrationEffect.OneShot(TEST_TIMING, TEST_AMPLITUDE); - VibrationEffect.Waveform waveform = new VibrationEffect.Waveform( - new long[] { TEST_TIMING }, new int[]{ TEST_AMPLITUDE }, -1); - VibrationEffect.Composed composed = + VibrationEffect.Composed effect = (VibrationEffect.Composed) VibrationEffect.startComposition() - .addPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK, - TEST_AMPLITUDE / 255f) + .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 0.5f, 1) .compose(); - assertEquals(oneShot.scale(0.8f).getAmplitude(), - waveform.scale(0.8f).getAmplitudes()[0], - AMPLITUDE_SCALE_TOLERANCE); - assertEquals(oneShot.scale(1.2f).getAmplitude() / 255f, - composed.scale(1.2f).getPrimitiveEffects().get(0).scale, - INTENSITY_SCALE_TOLERANCE); - } + VibrationEffect.Composed scaledUp = effect.scale(1.5f); + assertTrue(0.5f < ((PrimitiveSegment) scaledUp.getSegments().get(0)).getScale()); - @Test - public void testScaleOnMaxAmplitude() { - VibrationEffect.OneShot oneShot = new VibrationEffect.OneShot( - TEST_TIMING, VibrationEffect.MAX_AMPLITUDE); - VibrationEffect.Waveform waveform = new VibrationEffect.Waveform( - new long[]{TEST_TIMING}, new int[]{VibrationEffect.MAX_AMPLITUDE}, -1); - VibrationEffect.Composed composed = - (VibrationEffect.Composed) VibrationEffect.startComposition() - .addPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK) - .compose(); - - // Scale up does NOT scale MAX_AMPLITUDE - assertEquals(VibrationEffect.MAX_AMPLITUDE, oneShot.scale(1.1f).getAmplitude()); - assertEquals(VibrationEffect.MAX_AMPLITUDE, waveform.scale(1.2f).getAmplitudes()[0]); - assertEquals(1f, - composed.scale(1.4f).getPrimitiveEffects().get(0).scale, - INTENSITY_SCALE_TOLERANCE); // This needs tolerance for float point comparison. - - // Scale down does scale MAX_AMPLITUDE - assertEquals(216, oneShot.scale(0.9f).getAmplitude(), AMPLITUDE_SCALE_TOLERANCE); - assertEquals(180, waveform.scale(0.8f).getAmplitudes()[0], AMPLITUDE_SCALE_TOLERANCE); - assertEquals(0.57f, composed.scale(0.7f).getPrimitiveEffects().get(0).scale, - INTENSITY_SCALE_TOLERANCE); - } - - @Test - public void getEffectStrength_returnsValueFromConstructor() { - VibrationEffect.Prebaked effect = new VibrationEffect.Prebaked(VibrationEffect.EFFECT_CLICK, - VibrationEffect.EFFECT_STRENGTH_LIGHT, null); - Assert.assertEquals(VibrationEffect.EFFECT_STRENGTH_LIGHT, effect.getEffectStrength()); - } - - @Test - public void getFallbackEffect_withFallbackDisabled_isNull() { - VibrationEffect fallback = VibrationEffect.createOneShot(100, 100); - VibrationEffect.Prebaked effect = new VibrationEffect.Prebaked(VibrationEffect.EFFECT_CLICK, - false, VibrationEffect.EFFECT_STRENGTH_LIGHT); - Assert.assertNull(effect.getFallbackEffect()); - } - - @Test - public void getFallbackEffect_withoutEffectSet_isNull() { - VibrationEffect.Prebaked effect = new VibrationEffect.Prebaked(VibrationEffect.EFFECT_CLICK, - true, VibrationEffect.EFFECT_STRENGTH_LIGHT); - Assert.assertNull(effect.getFallbackEffect()); - } - - @Test - public void getFallbackEffect_withFallback_returnsValueFromConstructor() { - VibrationEffect fallback = VibrationEffect.createOneShot(100, 100); - VibrationEffect.Prebaked effect = new VibrationEffect.Prebaked(VibrationEffect.EFFECT_CLICK, - VibrationEffect.EFFECT_STRENGTH_LIGHT, fallback); - Assert.assertEquals(fallback, effect.getFallbackEffect()); + VibrationEffect.Composed scaledDown = effect.scale(0.5f); + assertTrue(0.5f > ((PrimitiveSegment) scaledDown.getSegments().get(0)).getScale()); } private Resources mockRingtoneResources() { diff --git a/core/tests/coretests/src/android/os/vibrator/PrebakedSegmentTest.java b/core/tests/coretests/src/android/os/vibrator/PrebakedSegmentTest.java new file mode 100644 index 0000000000000..de80812f9c29c --- /dev/null +++ b/core/tests/coretests/src/android/os/vibrator/PrebakedSegmentTest.java @@ -0,0 +1,108 @@ +/* + * Copyright (C) 2021 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 static junit.framework.Assert.assertEquals; +import static junit.framework.Assert.assertSame; +import static junit.framework.Assert.assertTrue; + +import static org.testng.Assert.assertNotEquals; +import static org.testng.Assert.assertThrows; + +import android.os.Parcel; +import android.os.VibrationEffect; +import android.platform.test.annotations.Presubmit; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.junit.MockitoJUnitRunner; + +@Presubmit +@RunWith(MockitoJUnitRunner.class) +public class PrebakedSegmentTest { + + @Test + public void testCreation() { + PrebakedSegment prebaked = new PrebakedSegment( + 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()); + } + + @Test + public void testSerialization() { + PrebakedSegment original = new PrebakedSegment( + VibrationEffect.EFFECT_CLICK, true, VibrationEffect.EFFECT_STRENGTH_MEDIUM); + Parcel parcel = Parcel.obtain(); + original.writeToParcel(parcel, 0); + parcel.setDataPosition(0); + assertEquals(original, PrebakedSegment.CREATOR.createFromParcel(parcel)); + } + + @Test + public void testValidate() { + new PrebakedSegment(VibrationEffect.EFFECT_CLICK, true, + VibrationEffect.EFFECT_STRENGTH_MEDIUM).validate(); + + assertThrows(IllegalArgumentException.class, + () -> new PrebakedSegment(1000, true, VibrationEffect.EFFECT_STRENGTH_MEDIUM) + .validate()); + assertThrows(IllegalArgumentException.class, + () -> new PrebakedSegment(VibrationEffect.EFFECT_TICK, false, 1000) + .validate()); + } + + @Test + public void testResolve_ignoresAndReturnsSameEffect() { + PrebakedSegment prebaked = new PrebakedSegment( + VibrationEffect.EFFECT_CLICK, true, VibrationEffect.EFFECT_STRENGTH_MEDIUM); + assertSame(prebaked, prebaked.resolve(1000)); + } + + @Test + public void testApplyEffectStrength() { + PrebakedSegment medium = new PrebakedSegment( + VibrationEffect.EFFECT_THUD, true, VibrationEffect.EFFECT_STRENGTH_MEDIUM); + + PrebakedSegment light = medium.applyEffectStrength(VibrationEffect.EFFECT_STRENGTH_LIGHT); + assertNotEquals(medium, light); + assertEquals(medium.getEffectId(), light.getEffectId()); + assertEquals(medium.shouldFallback(), light.shouldFallback()); + assertEquals(VibrationEffect.EFFECT_STRENGTH_LIGHT, light.getEffectStrength()); + + PrebakedSegment strong = medium.applyEffectStrength(VibrationEffect.EFFECT_STRENGTH_STRONG); + assertNotEquals(medium, strong); + assertEquals(medium.getEffectId(), strong.getEffectId()); + assertEquals(medium.shouldFallback(), strong.shouldFallback()); + assertEquals(VibrationEffect.EFFECT_STRENGTH_STRONG, strong.getEffectStrength()); + + assertSame(medium, medium.applyEffectStrength(VibrationEffect.EFFECT_STRENGTH_MEDIUM)); + // Invalid vibration effect strength is ignored. + assertSame(medium, medium.applyEffectStrength(1000)); + } + + @Test + public void testScale_ignoresAndReturnsSameEffect() { + PrebakedSegment prebaked = new PrebakedSegment( + VibrationEffect.EFFECT_CLICK, true, VibrationEffect.EFFECT_STRENGTH_MEDIUM); + assertSame(prebaked, prebaked.scale(0.5f)); + } +} diff --git a/core/tests/coretests/src/android/os/vibrator/PrimitiveSegmentTest.java b/core/tests/coretests/src/android/os/vibrator/PrimitiveSegmentTest.java new file mode 100644 index 0000000000000..538655bb394bc --- /dev/null +++ b/core/tests/coretests/src/android/os/vibrator/PrimitiveSegmentTest.java @@ -0,0 +1,130 @@ +/* + * Copyright (C) 2021 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 static junit.framework.Assert.assertEquals; +import static junit.framework.Assert.assertSame; +import static junit.framework.Assert.assertTrue; + +import static org.testng.Assert.assertThrows; + +import android.os.Parcel; +import android.os.VibrationEffect; +import android.platform.test.annotations.Presubmit; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.junit.MockitoJUnitRunner; + +@Presubmit +@RunWith(MockitoJUnitRunner.class) +public class PrimitiveSegmentTest { + private static final float TOLERANCE = 1e-2f; + + @Test + public void testCreation() { + PrimitiveSegment primitive = new PrimitiveSegment( + 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); + } + + @Test + public void testSerialization() { + PrimitiveSegment original = new PrimitiveSegment( + VibrationEffect.Composition.PRIMITIVE_CLICK, 1, 10); + Parcel parcel = Parcel.obtain(); + original.writeToParcel(parcel, 0); + parcel.setDataPosition(0); + assertEquals(original, PrimitiveSegment.CREATOR.createFromParcel(parcel)); + } + + @Test + public void testValidate() { + new PrimitiveSegment(VibrationEffect.Composition.PRIMITIVE_CLICK, 1, 0).validate(); + + assertThrows(IllegalArgumentException.class, + () -> new PrimitiveSegment(1000, 0, 10).validate()); + assertThrows(IllegalArgumentException.class, + () -> new PrimitiveSegment(VibrationEffect.Composition.PRIMITIVE_NOOP, -1, 0) + .validate()); + assertThrows(IllegalArgumentException.class, + () -> new PrimitiveSegment(VibrationEffect.Composition.PRIMITIVE_NOOP, 1, -1) + .validate()); + } + + @Test + public void testResolve_ignoresAndReturnsSameEffect() { + PrimitiveSegment primitive = new PrimitiveSegment( + VibrationEffect.Composition.PRIMITIVE_CLICK, 1, 0); + assertSame(primitive, primitive.resolve(1000)); + } + + @Test + public void testApplyEffectStrength_ignoresAndReturnsSameEffect() { + PrimitiveSegment primitive = new PrimitiveSegment( + VibrationEffect.Composition.PRIMITIVE_CLICK, 1, 0); + assertSame(primitive, + primitive.applyEffectStrength(VibrationEffect.EFFECT_STRENGTH_STRONG)); + } + + @Test + public void testScale_fullPrimitiveScaleValue() { + PrimitiveSegment initial = new PrimitiveSegment( + VibrationEffect.Composition.PRIMITIVE_CLICK, 1, 0); + + assertEquals(1f, initial.scale(1).getScale(), TOLERANCE); + assertEquals(0.34f, 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(0.53f, 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.71f, initial.scale(0.8f).getScale(), TOLERANCE); + assertEquals(0.84f, initial.scale(0.8f).scale(1.25f).getScale(), TOLERANCE); + } + + @Test + public void testScale_halfPrimitiveScaleValue() { + PrimitiveSegment initial = new PrimitiveSegment( + VibrationEffect.Composition.PRIMITIVE_CLICK, 0.5f, 0); + + assertEquals(0.5f, initial.scale(1).getScale(), TOLERANCE); + assertEquals(0.17f, initial.scale(0.5f).getScale(), TOLERANCE); + // The original value was not scaled up, so this only scales it down. + assertEquals(0.86f, initial.scale(1.5f).getScale(), TOLERANCE); + assertEquals(0.47f, 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.35f, initial.scale(0.8f).getScale(), TOLERANCE); + assertEquals(0.5f, initial.scale(0.8f).scale(1.25f).getScale(), TOLERANCE); + } + + @Test + public void testScale_zeroPrimitiveScaleValue() { + PrimitiveSegment initial = new PrimitiveSegment( + VibrationEffect.Composition.PRIMITIVE_CLICK, 0, 0); + + assertEquals(0f, initial.scale(1).getScale(), TOLERANCE); + assertEquals(0f, initial.scale(0.5f).getScale(), TOLERANCE); + assertEquals(0f, initial.scale(1.5f).getScale(), TOLERANCE); + assertEquals(0f, initial.scale(1.5f).scale(2 / 3f).getScale(), TOLERANCE); + assertEquals(0f, initial.scale(0.8f).scale(1.25f).getScale(), TOLERANCE); + } +} diff --git a/core/tests/coretests/src/android/os/vibrator/StepSegmentTest.java b/core/tests/coretests/src/android/os/vibrator/StepSegmentTest.java new file mode 100644 index 0000000000000..188b6c166102b --- /dev/null +++ b/core/tests/coretests/src/android/os/vibrator/StepSegmentTest.java @@ -0,0 +1,142 @@ +/* + * Copyright (C) 2021 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 static junit.framework.Assert.assertEquals; +import static junit.framework.Assert.assertFalse; +import static junit.framework.Assert.assertSame; +import static junit.framework.Assert.assertTrue; + +import static org.testng.Assert.assertThrows; + +import android.os.Parcel; +import android.os.VibrationEffect; +import android.platform.test.annotations.Presubmit; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.junit.MockitoJUnitRunner; + +@Presubmit +@RunWith(MockitoJUnitRunner.class) +public class StepSegmentTest { + private static final float TOLERANCE = 1e-2f; + + @Test + public void testCreation() { + StepSegment step = new StepSegment(/* amplitude= */ 1f, /* duration= */ 100); + + assertEquals(100, step.getDuration()); + assertTrue(step.hasNonZeroAmplitude()); + assertEquals(1f, step.getAmplitude()); + } + + @Test + public void testSerialization() { + StepSegment original = new StepSegment(0.5f, 10); + Parcel parcel = Parcel.obtain(); + original.writeToParcel(parcel, 0); + parcel.setDataPosition(0); + assertEquals(original, StepSegment.CREATOR.createFromParcel(parcel)); + } + + @Test + public void testValidate() { + new StepSegment(/* amplitude= */ 0f, /* duration= */ 100).validate(); + + assertThrows(IllegalArgumentException.class, + () -> new StepSegment(/* amplitude= */ -2, 10).validate()); + assertThrows(IllegalArgumentException.class, + () -> new StepSegment(/* amplitude= */ 2, 10).validate()); + assertThrows(IllegalArgumentException.class, + () -> new StepSegment(2, /* duration= */ -1).validate()); + } + + @Test + public void testHasNonZeroAmplitude() { + assertTrue(new StepSegment(1f, 0).hasNonZeroAmplitude()); + assertTrue(new StepSegment(0.01f, 0).hasNonZeroAmplitude()); + assertTrue(new StepSegment(VibrationEffect.DEFAULT_AMPLITUDE, 0).hasNonZeroAmplitude()); + assertFalse(new StepSegment(0, 0).hasNonZeroAmplitude()); + } + + @Test + public void testResolve() { + StepSegment original = new StepSegment(VibrationEffect.DEFAULT_AMPLITUDE, 0); + assertEquals(1f, original.resolve(VibrationEffect.MAX_AMPLITUDE).getAmplitude()); + assertEquals(0.2f, original.resolve(51).getAmplitude(), TOLERANCE); + + StepSegment resolved = new StepSegment(0, 0); + assertSame(resolved, resolved.resolve(100)); + + assertThrows(IllegalArgumentException.class, () -> resolved.resolve(1000)); + } + + @Test + public void testApplyEffectStrength_ignoresAndReturnsSameEffect() { + StepSegment step = new StepSegment(VibrationEffect.DEFAULT_AMPLITUDE, 0); + assertSame(step, step.applyEffectStrength(VibrationEffect.EFFECT_STRENGTH_STRONG)); + } + + @Test + public void testScale_fullAmplitude() { + StepSegment initial = new StepSegment(1f, 0); + + assertEquals(1f, initial.scale(1).getAmplitude(), TOLERANCE); + assertEquals(0.34f, initial.scale(0.5f).getAmplitude(), TOLERANCE); + // The original value was not scaled up, so this only scales it down. + assertEquals(1f, initial.scale(1.5f).getAmplitude(), TOLERANCE); + assertEquals(0.53f, initial.scale(1.5f).scale(2 / 3f).getAmplitude(), TOLERANCE); + // Does not restore to the exact original value because scale up is a bit offset. + assertEquals(0.71f, initial.scale(0.8f).getAmplitude(), TOLERANCE); + assertEquals(0.84f, initial.scale(0.8f).scale(1.25f).getAmplitude(), TOLERANCE); + } + + @Test + public void testScale_halfAmplitude() { + StepSegment initial = new StepSegment(0.5f, 0); + + assertEquals(0.5f, initial.scale(1).getAmplitude(), TOLERANCE); + assertEquals(0.17f, initial.scale(0.5f).getAmplitude(), TOLERANCE); + // The original value was not scaled up, so this only scales it down. + assertEquals(0.86f, initial.scale(1.5f).getAmplitude(), TOLERANCE); + assertEquals(0.47f, initial.scale(1.5f).scale(2 / 3f).getAmplitude(), TOLERANCE); + // Does not restore to the exact original value because scale up is a bit offset. + assertEquals(0.35f, initial.scale(0.8f).getAmplitude(), TOLERANCE); + assertEquals(0.5f, initial.scale(0.8f).scale(1.25f).getAmplitude(), TOLERANCE); + } + + @Test + public void testScale_zeroAmplitude() { + StepSegment initial = new StepSegment(0, 0); + + assertEquals(0f, initial.scale(1).getAmplitude(), TOLERANCE); + assertEquals(0f, initial.scale(0.5f).getAmplitude(), TOLERANCE); + assertEquals(0f, initial.scale(1.5f).getAmplitude(), TOLERANCE); + } + + @Test + public void testScale_defaultAmplitude() { + StepSegment initial = new StepSegment(VibrationEffect.DEFAULT_AMPLITUDE, 0); + + assertEquals(VibrationEffect.DEFAULT_AMPLITUDE, initial.scale(1).getAmplitude(), TOLERANCE); + assertEquals(VibrationEffect.DEFAULT_AMPLITUDE, initial.scale(0.5f).getAmplitude(), + TOLERANCE); + assertEquals(VibrationEffect.DEFAULT_AMPLITUDE, initial.scale(1.5f).getAmplitude(), + TOLERANCE); + } +} diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java index 092502709f34d..0f137418a89cc 100644 --- a/services/core/java/com/android/server/input/InputManagerService.java +++ b/services/core/java/com/android/server/input/InputManagerService.java @@ -76,6 +76,8 @@ import android.os.ShellCallback; import android.os.SystemProperties; import android.os.UserHandle; import android.os.VibrationEffect; +import android.os.vibrator.StepSegment; +import android.os.vibrator.VibrationEffectSegment; import android.provider.DeviceConfig; import android.provider.Settings; import android.provider.Settings.SettingNotFoundException; @@ -1917,9 +1919,9 @@ public class InputManagerService extends IInputManager.Stub } private static class VibrationInfo { - private long[] mPattern = new long[0]; - private int[] mAmplitudes = new int[0]; - private int mRepeat = -1; + private final long[] mPattern; + private final int[] mAmplitudes; + private final int mRepeat; public long[] getPattern() { return mPattern; @@ -1934,40 +1936,55 @@ public class InputManagerService extends IInputManager.Stub } VibrationInfo(VibrationEffect effect) { - // First replace prebaked effects with its fallback, if any available. - if (effect instanceof VibrationEffect.Prebaked) { - VibrationEffect fallback = ((VibrationEffect.Prebaked) effect).getFallbackEffect(); - if (fallback != null) { - effect = fallback; + long[] pattern = null; + int[] amplitudes = null; + int patternRepeatIndex = -1; + int amplitudeCount = -1; + + if (effect instanceof VibrationEffect.Composed) { + VibrationEffect.Composed composed = (VibrationEffect.Composed) effect; + int segmentCount = composed.getSegments().size(); + pattern = new long[segmentCount]; + amplitudes = new int[segmentCount]; + patternRepeatIndex = composed.getRepeatIndex(); + amplitudeCount = 0; + for (int i = 0; i < segmentCount; i++) { + VibrationEffectSegment segment = composed.getSegments().get(i); + if (composed.getRepeatIndex() == i) { + patternRepeatIndex = amplitudeCount; + } + if (!(segment instanceof StepSegment)) { + Slog.w(TAG, "Input devices don't support segment " + segment); + amplitudeCount = -1; + break; + } + float amplitude = ((StepSegment) segment).getAmplitude(); + if (Float.compare(amplitude, VibrationEffect.DEFAULT_AMPLITUDE) == 0) { + amplitudes[amplitudeCount] = DEFAULT_VIBRATION_MAGNITUDE; + } else { + amplitudes[amplitudeCount] = + (int) (amplitude * VibrationEffect.MAX_AMPLITUDE); + } + pattern[amplitudeCount++] = segment.getDuration(); } } - if (effect instanceof VibrationEffect.OneShot) { - VibrationEffect.OneShot oneShot = (VibrationEffect.OneShot) effect; - mPattern = new long[] { 0, oneShot.getDuration() }; - int amplitude = oneShot.getAmplitude(); - // android framework uses DEFAULT_AMPLITUDE to signal that the vibration - // should use some built-in default value, denoted here as - // DEFAULT_VIBRATION_MAGNITUDE - if (amplitude == VibrationEffect.DEFAULT_AMPLITUDE) { - amplitude = DEFAULT_VIBRATION_MAGNITUDE; - } - mAmplitudes = new int[] { 0, amplitude }; + + if (amplitudeCount < 0) { + Slog.w(TAG, "Only oneshot and step waveforms are supported on input devices"); + mPattern = new long[0]; + mAmplitudes = new int[0]; mRepeat = -1; - } else if (effect instanceof VibrationEffect.Waveform) { - VibrationEffect.Waveform waveform = (VibrationEffect.Waveform) effect; - mPattern = waveform.getTimings(); - mAmplitudes = waveform.getAmplitudes(); - for (int i = 0; i < mAmplitudes.length; i++) { - if (mAmplitudes[i] == VibrationEffect.DEFAULT_AMPLITUDE) { - mAmplitudes[i] = DEFAULT_VIBRATION_MAGNITUDE; - } - } - mRepeat = waveform.getRepeatIndex(); - if (mRepeat >= mPattern.length) { - throw new ArrayIndexOutOfBoundsException(); - } } else { - Slog.w(TAG, "Pre-baked and composed effects aren't supported on input devices"); + mRepeat = patternRepeatIndex; + mPattern = new long[amplitudeCount]; + mAmplitudes = new int[amplitudeCount]; + System.arraycopy(pattern, 0, mPattern, 0, amplitudeCount); + System.arraycopy(amplitudes, 0, mAmplitudes, 0, amplitudeCount); + if (mRepeat >= mPattern.length) { + throw new ArrayIndexOutOfBoundsException("Repeat index " + mRepeat + + " must be within the bounds of the pattern.length " + + mPattern.length); + } } } } diff --git a/services/core/java/com/android/server/vibrator/Vibration.java b/services/core/java/com/android/server/vibrator/Vibration.java index e84ee672bf0ff..1e897ea207ede 100644 --- a/services/core/java/com/android/server/vibrator/Vibration.java +++ b/services/core/java/com/android/server/vibrator/Vibration.java @@ -16,17 +16,23 @@ package com.android.server.vibrator; -import android.annotation.NonNull; import android.annotation.Nullable; import android.os.CombinedVibrationEffect; import android.os.IBinder; import android.os.SystemClock; import android.os.VibrationAttributes; import android.os.VibrationEffect; +import android.os.vibrator.PrebakedSegment; +import android.os.vibrator.PrimitiveSegment; +import android.os.vibrator.StepSegment; +import android.os.vibrator.VibrationEffectSegment; +import android.util.SparseArray; import android.util.proto.ProtoOutputStream; import java.text.SimpleDateFormat; import java.util.Date; +import java.util.List; +import java.util.function.Function; /** Represents a vibration request to the vibrator service. */ final class Vibration { @@ -61,6 +67,7 @@ final class Vibration { public final String opPkg; public final String reason; public final IBinder token; + public final SparseArray mFallbacks = new SparseArray<>(); /** The actual effect to be played. */ @Nullable @@ -113,17 +120,70 @@ final class Vibration { } /** - * Replace this vibration effect if given {@code scaledEffect} is different, preserving the - * original one for debug purposes. + * Return the effect to be played when given prebaked effect id is not supported by the + * vibrator. */ - public void updateEffect(@NonNull CombinedVibrationEffect newEffect) { - if (newEffect.equals(mEffect)) { - return; + @Nullable + public VibrationEffect getFallback(int effectId) { + return mFallbacks.get(effectId); + } + + /** + * Add a fallback {@link VibrationEffect} to be played when given effect id is not supported, + * which might be necessary for replacement in realtime. + */ + public void addFallback(int effectId, VibrationEffect effect) { + mFallbacks.put(effectId, effect); + } + + /** + * Applied update function to the current effect held by this vibration, and to each fallback + * effect added. + */ + public void updateEffects(Function updateFn) { + CombinedVibrationEffect newEffect = transformCombinedEffect(mEffect, updateFn); + if (!newEffect.equals(mEffect)) { + if (mOriginalEffect == null) { + mOriginalEffect = mEffect; + } + mEffect = newEffect; } - if (mOriginalEffect == null) { - mOriginalEffect = mEffect; + for (int i = 0; i < mFallbacks.size(); i++) { + mFallbacks.setValueAt(i, updateFn.apply(mFallbacks.valueAt(i))); + } + } + + /** + * Creates a new {@link CombinedVibrationEffect} by applying the given transformation function + * to each {@link VibrationEffect}. + */ + private static CombinedVibrationEffect transformCombinedEffect( + CombinedVibrationEffect combinedEffect, Function fn) { + if (combinedEffect instanceof CombinedVibrationEffect.Mono) { + VibrationEffect effect = ((CombinedVibrationEffect.Mono) combinedEffect).getEffect(); + return CombinedVibrationEffect.createSynced(fn.apply(effect)); + } else if (combinedEffect instanceof CombinedVibrationEffect.Stereo) { + SparseArray effects = + ((CombinedVibrationEffect.Stereo) combinedEffect).getEffects(); + CombinedVibrationEffect.SyncedCombination combination = + CombinedVibrationEffect.startSynced(); + for (int i = 0; i < effects.size(); i++) { + combination.addVibrator(effects.keyAt(i), fn.apply(effects.valueAt(i))); + } + return combination.combine(); + } else if (combinedEffect instanceof CombinedVibrationEffect.Sequential) { + List effects = + ((CombinedVibrationEffect.Sequential) combinedEffect).getEffects(); + CombinedVibrationEffect.SequentialCombination combination = + CombinedVibrationEffect.startSequential(); + for (CombinedVibrationEffect effect : effects) { + combination.addNext(transformCombinedEffect(effect, fn)); + } + return combination.combine(); + } else { + // Unknown combination, return same effect. + return combinedEffect; } - mEffect = newEffect; } /** Return true is current status is different from {@link Status#RUNNING}. */ @@ -272,57 +332,49 @@ final class Vibration { private void dumpEffect( ProtoOutputStream proto, long fieldId, VibrationEffect effect) { final long token = proto.start(fieldId); - if (effect instanceof VibrationEffect.OneShot) { - dumpEffect(proto, VibrationEffectProto.ONESHOT, (VibrationEffect.OneShot) effect); - } else if (effect instanceof VibrationEffect.Waveform) { - dumpEffect(proto, VibrationEffectProto.WAVEFORM, (VibrationEffect.Waveform) effect); - } else if (effect instanceof VibrationEffect.Prebaked) { - dumpEffect(proto, VibrationEffectProto.PREBAKED, (VibrationEffect.Prebaked) effect); - } else if (effect instanceof VibrationEffect.Composed) { - dumpEffect(proto, VibrationEffectProto.COMPOSED, (VibrationEffect.Composed) effect); + VibrationEffect.Composed composed = (VibrationEffect.Composed) effect; + for (VibrationEffectSegment segment : composed.getSegments()) { + dumpEffect(proto, VibrationEffectProto.SEGMENTS, segment); } + proto.write(VibrationEffectProto.REPEAT, composed.getRepeatIndex()); proto.end(token); } private void dumpEffect(ProtoOutputStream proto, long fieldId, - VibrationEffect.OneShot effect) { + VibrationEffectSegment segment) { final long token = proto.start(fieldId); - proto.write(OneShotProto.DURATION, (int) effect.getDuration()); - proto.write(OneShotProto.AMPLITUDE, effect.getAmplitude()); + if (segment instanceof StepSegment) { + dumpEffect(proto, SegmentProto.STEP, (StepSegment) segment); + } else if (segment instanceof PrebakedSegment) { + dumpEffect(proto, SegmentProto.PREBAKED, (PrebakedSegment) segment); + } else if (segment instanceof PrimitiveSegment) { + dumpEffect(proto, SegmentProto.PRIMITIVE, (PrimitiveSegment) segment); + } + proto.end(token); + } + + private void dumpEffect(ProtoOutputStream proto, long fieldId, StepSegment segment) { + final long token = proto.start(fieldId); + proto.write(StepSegmentProto.DURATION, segment.getDuration()); + proto.write(StepSegmentProto.AMPLITUDE, segment.getAmplitude()); proto.end(token); } private void dumpEffect(ProtoOutputStream proto, long fieldId, - VibrationEffect.Waveform effect) { + PrebakedSegment segment) { final long token = proto.start(fieldId); - for (long timing : effect.getTimings()) { - proto.write(WaveformProto.TIMINGS, (int) timing); - } - for (int amplitude : effect.getAmplitudes()) { - proto.write(WaveformProto.AMPLITUDES, amplitude); - } - proto.write(WaveformProto.REPEAT, effect.getRepeatIndex() >= 0); + proto.write(PrebakedSegmentProto.EFFECT_ID, segment.getEffectId()); + proto.write(PrebakedSegmentProto.EFFECT_STRENGTH, segment.getEffectStrength()); + proto.write(PrebakedSegmentProto.FALLBACK, segment.shouldFallback()); proto.end(token); } private void dumpEffect(ProtoOutputStream proto, long fieldId, - VibrationEffect.Prebaked effect) { + PrimitiveSegment segment) { final long token = proto.start(fieldId); - proto.write(PrebakedProto.EFFECT_ID, effect.getId()); - proto.write(PrebakedProto.EFFECT_STRENGTH, effect.getEffectStrength()); - proto.write(PrebakedProto.FALLBACK, effect.shouldFallback()); - proto.end(token); - } - - private void dumpEffect(ProtoOutputStream proto, long fieldId, - VibrationEffect.Composed effect) { - final long token = proto.start(fieldId); - for (VibrationEffect.Composition.PrimitiveEffect primitive : - effect.getPrimitiveEffects()) { - proto.write(ComposedProto.EFFECT_IDS, primitive.id); - proto.write(ComposedProto.EFFECT_SCALES, primitive.scale); - proto.write(ComposedProto.DELAYS, primitive.delay); - } + proto.write(PrimitiveSegmentProto.PRIMITIVE_ID, segment.getPrimitiveId()); + proto.write(PrimitiveSegmentProto.SCALE, segment.getScale()); + proto.write(PrimitiveSegmentProto.DELAY, segment.getDelay()); proto.end(token); } } diff --git a/services/core/java/com/android/server/vibrator/VibrationScaler.java b/services/core/java/com/android/server/vibrator/VibrationScaler.java index 10393f682279c..f481772d7a7f7 100644 --- a/services/core/java/com/android/server/vibrator/VibrationScaler.java +++ b/services/core/java/com/android/server/vibrator/VibrationScaler.java @@ -18,16 +18,13 @@ package com.android.server.vibrator; import android.content.Context; import android.hardware.vibrator.V1_0.EffectStrength; -import android.os.CombinedVibrationEffect; import android.os.IExternalVibratorService; import android.os.VibrationEffect; import android.os.Vibrator; +import android.os.vibrator.PrebakedSegment; import android.util.Slog; import android.util.SparseArray; -import java.util.List; -import java.util.Objects; - /** Controls vibration scaling. */ final class VibrationScaler { private static final String TAG = "VibrationScaler"; @@ -89,43 +86,6 @@ final class VibrationScaler { } } - /** - * Scale a {@link CombinedVibrationEffect} based on the given usage hint for this vibration. - * - * @param combinedEffect the effect to be scaled - * @param usageHint one of VibrationAttributes.USAGE_* - * @return The same given effect, if no changes were made, or a new - * {@link CombinedVibrationEffect} with resolved and scaled amplitude - */ - public T scale(CombinedVibrationEffect combinedEffect, - int usageHint) { - if (combinedEffect instanceof CombinedVibrationEffect.Mono) { - VibrationEffect effect = ((CombinedVibrationEffect.Mono) combinedEffect).getEffect(); - return (T) CombinedVibrationEffect.createSynced(scale(effect, usageHint)); - } else if (combinedEffect instanceof CombinedVibrationEffect.Stereo) { - SparseArray effects = - ((CombinedVibrationEffect.Stereo) combinedEffect).getEffects(); - CombinedVibrationEffect.SyncedCombination combination = - CombinedVibrationEffect.startSynced(); - for (int i = 0; i < effects.size(); i++) { - combination.addVibrator(effects.keyAt(i), scale(effects.valueAt(i), usageHint)); - } - return (T) combination.combine(); - } else if (combinedEffect instanceof CombinedVibrationEffect.Sequential) { - List effects = - ((CombinedVibrationEffect.Sequential) combinedEffect).getEffects(); - CombinedVibrationEffect.SequentialCombination combination = - CombinedVibrationEffect.startSequential(); - for (CombinedVibrationEffect effect : effects) { - combination.addNext(scale(effect, usageHint)); - } - return (T) combination.combine(); - } else { - // Unknown combination, return same effect. - return (T) combinedEffect; - } - } - /** * Scale a {@link VibrationEffect} based on the given usage hint for this vibration. * @@ -135,33 +95,10 @@ final class VibrationScaler { * resolved and scaled amplitude */ public T scale(VibrationEffect effect, int usageHint) { - if (effect instanceof VibrationEffect.Prebaked) { - // Prebaked effects are always just a direct translation to EffectStrength. - int intensity = mSettingsController.getCurrentIntensity(usageHint); - int newStrength = intensityToEffectStrength(intensity); - VibrationEffect.Prebaked prebaked = (VibrationEffect.Prebaked) effect; - int strength = prebaked.getEffectStrength(); - VibrationEffect fallback = prebaked.getFallbackEffect(); - - if (fallback != null) { - VibrationEffect scaledFallback = scale(fallback, usageHint); - if (strength == newStrength && Objects.equals(fallback, scaledFallback)) { - return (T) prebaked; - } - - return (T) new VibrationEffect.Prebaked(prebaked.getId(), newStrength, - scaledFallback); - } else if (strength == newStrength) { - return (T) prebaked; - } else { - return (T) new VibrationEffect.Prebaked(prebaked.getId(), prebaked.shouldFallback(), - newStrength); - } - } - - effect = effect.resolve(mDefaultVibrationAmplitude); int defaultIntensity = mSettingsController.getDefaultIntensity(usageHint); int currentIntensity = mSettingsController.getCurrentIntensity(usageHint); + int newEffectStrength = intensityToEffectStrength(currentIntensity); + effect = effect.applyEffectStrength(newEffectStrength).resolve(mDefaultVibrationAmplitude); ScaleLevel scale = mScaleLevels.get(currentIntensity - defaultIntensity); if (scale == null) { @@ -171,7 +108,21 @@ final class VibrationScaler { return (T) effect; } - return effect.scale(scale.factor); + return (T) effect.scale(scale.factor); + } + + /** + * Scale a {@link PrebakedSegment} based on the given usage hint for this vibration. + * + * @param prebaked the prebaked segment to be scaled + * @param usageHint one of VibrationAttributes.USAGE_* + * @return The same segment if no changes were made, or a new {@link PrebakedSegment} with + * updated effect strength + */ + public PrebakedSegment scale(PrebakedSegment prebaked, int usageHint) { + int currentIntensity = mSettingsController.getCurrentIntensity(usageHint); + int newEffectStrength = intensityToEffectStrength(currentIntensity); + return prebaked.applyEffectStrength(newEffectStrength); } /** Mapping of Vibrator.VIBRATION_INTENSITY_* values to {@link EffectStrength}. */ diff --git a/services/core/java/com/android/server/vibrator/VibrationThread.java b/services/core/java/com/android/server/vibrator/VibrationThread.java index b90408fe53717..18063de9f0835 100644 --- a/services/core/java/com/android/server/vibrator/VibrationThread.java +++ b/services/core/java/com/android/server/vibrator/VibrationThread.java @@ -28,6 +28,10 @@ import android.os.SystemClock; import android.os.Trace; import android.os.VibrationEffect; import android.os.WorkSource; +import android.os.vibrator.PrebakedSegment; +import android.os.vibrator.PrimitiveSegment; +import android.os.vibrator.StepSegment; +import android.os.vibrator.VibrationEffectSegment; import android.util.Slog; import android.util.SparseArray; @@ -248,22 +252,26 @@ final class VibrationThread extends Thread implements IBinder.DeathRecipient { * Get the duration the vibrator will be on for given {@code waveform}, starting at {@code * startIndex} until the next time it's vibrating amplitude is zero. */ - private static long getVibratorOnDuration(VibrationEffect.Waveform waveform, int startIndex) { - long[] timings = waveform.getTimings(); - int[] amplitudes = waveform.getAmplitudes(); - int repeatIndex = waveform.getRepeatIndex(); + private static long getVibratorOnDuration(VibrationEffect.Composed effect, int startIndex) { + List segments = effect.getSegments(); + int segmentCount = segments.size(); + int repeatIndex = effect.getRepeatIndex(); int i = startIndex; long timing = 0; - while (timings[i] == 0 || amplitudes[i] != 0) { - timing += timings[i++]; - if (i >= timings.length) { - if (repeatIndex >= 0) { - i = repeatIndex; - // prevent infinite loop - repeatIndex = -1; - } else { - break; - } + while (i < segmentCount) { + if (!(segments.get(i) instanceof StepSegment)) { + break; + } + StepSegment stepSegment = (StepSegment) segments.get(i); + if (stepSegment.getAmplitude() == 0) { + break; + } + timing += stepSegment.getDuration(); + i++; + if (i == segmentCount && repeatIndex >= 0) { + i = repeatIndex; + // prevent infinite loop + repeatIndex = -1; } if (i == startIndex) { return 1000; @@ -620,22 +628,14 @@ final class VibrationThread extends Thread implements IBinder.DeathRecipient { } private long startVibrating(VibrationEffect effect, List nextSteps) { + VibrationEffect.Composed composed = (VibrationEffect.Composed) effect; + VibrationEffectSegment firstSegment = composed.getSegments().get(0); final long duration; final long now = SystemClock.uptimeMillis(); - if (effect instanceof VibrationEffect.OneShot) { - VibrationEffect.OneShot oneShot = (VibrationEffect.OneShot) effect; - duration = oneShot.getDuration(); - // Do NOT set amplitude here. This might be called between prepareSynced and - // triggerSynced, so the vibrator is not actually turned on here. - // The next steps will handle the amplitude after the vibrator has turned on. - controller.on(duration, mVibration.id); - nextSteps.add(new VibratorAmplitudeStep(now, controller, oneShot, - now + duration + CALLBACKS_EXTRA_TIMEOUT)); - } else if (effect instanceof VibrationEffect.Waveform) { - VibrationEffect.Waveform waveform = (VibrationEffect.Waveform) effect; + if (firstSegment instanceof StepSegment) { // Return the full duration of this waveform effect. - duration = waveform.getDuration(); - long onDuration = getVibratorOnDuration(waveform, 0); + duration = effect.getDuration(); + long onDuration = getVibratorOnDuration(composed, 0); if (onDuration > 0) { // Do NOT set amplitude here. This might be called between prepareSynced and // triggerSynced, so the vibrator is not actually turned on here. @@ -643,19 +643,31 @@ final class VibrationThread extends Thread implements IBinder.DeathRecipient { controller.on(onDuration, mVibration.id); } long offTime = onDuration > 0 ? now + onDuration + CALLBACKS_EXTRA_TIMEOUT : now; - nextSteps.add(new VibratorAmplitudeStep(now, controller, waveform, offTime)); - } else if (effect instanceof VibrationEffect.Prebaked) { - VibrationEffect.Prebaked prebaked = (VibrationEffect.Prebaked) effect; + nextSteps.add(new VibratorAmplitudeStep(now, controller, composed, offTime)); + } else if (firstSegment instanceof PrebakedSegment) { + PrebakedSegment prebaked = (PrebakedSegment) firstSegment; + VibrationEffect fallback = mVibration.getFallback(prebaked.getEffectId()); duration = controller.on(prebaked, mVibration.id); if (duration > 0) { nextSteps.add(new VibratorOffStep(now + duration + CALLBACKS_EXTRA_TIMEOUT, controller)); - } else if (prebaked.getFallbackEffect() != null) { - return startVibrating(prebaked.getFallbackEffect(), nextSteps); + } else if (prebaked.shouldFallback() && fallback != null) { + return startVibrating(fallback, nextSteps); } - } else if (effect instanceof VibrationEffect.Composed) { - VibrationEffect.Composed composed = (VibrationEffect.Composed) effect; - duration = controller.on(composed, mVibration.id); + } else if (firstSegment instanceof PrimitiveSegment) { + int segmentCount = composed.getSegments().size(); + PrimitiveSegment[] primitives = new PrimitiveSegment[segmentCount]; + for (int i = 0; i < segmentCount; i++) { + VibrationEffectSegment segment = composed.getSegments().get(i); + if (segment instanceof PrimitiveSegment) { + primitives[i] = (PrimitiveSegment) segment; + } else { + primitives[i] = new PrimitiveSegment( + VibrationEffect.Composition.PRIMITIVE_NOOP, + /* scale= */ 1, /* delay= */ 0); + } + } + duration = controller.on(primitives, mVibration.id); if (duration > 0) { nextSteps.add(new VibratorOffStep(now + duration + CALLBACKS_EXTRA_TIMEOUT, controller)); @@ -713,33 +725,22 @@ final class VibrationThread extends Thread implements IBinder.DeathRecipient { /** Represents a step to change the amplitude of the vibrator. */ private final class VibratorAmplitudeStep extends Step { public final VibratorController controller; - public final VibrationEffect.Waveform waveform; + public final VibrationEffect.Composed effect; public final int currentIndex; - public final long expectedVibratorStopTime; private long mNextVibratorStopTime; VibratorAmplitudeStep(long startTime, VibratorController controller, - VibrationEffect.OneShot oneShot, long expectedVibratorStopTime) { - this(startTime, controller, - (VibrationEffect.Waveform) VibrationEffect.createWaveform( - new long[]{oneShot.getDuration()}, new int[]{oneShot.getAmplitude()}, - /* repeat= */ -1), - expectedVibratorStopTime); + VibrationEffect.Composed effect, long expectedVibratorStopTime) { + this(startTime, controller, effect, /* index= */ 0, expectedVibratorStopTime); } VibratorAmplitudeStep(long startTime, VibratorController controller, - VibrationEffect.Waveform waveform, long expectedVibratorStopTime) { - this(startTime, controller, waveform, /* index= */ 0, expectedVibratorStopTime); - } - - VibratorAmplitudeStep(long startTime, VibratorController controller, - VibrationEffect.Waveform waveform, int index, long expectedVibratorStopTime) { + VibrationEffect.Composed effect, int index, long expectedVibratorStopTime) { super(startTime); this.controller = controller; - this.waveform = waveform; + this.effect = effect; this.currentIndex = index; - this.expectedVibratorStopTime = expectedVibratorStopTime; mNextVibratorStopTime = expectedVibratorStopTime; } @@ -759,11 +760,16 @@ final class VibrationThread extends Thread implements IBinder.DeathRecipient { long latency = SystemClock.uptimeMillis() - startTime; Slog.d(TAG, "Running amplitude step with " + latency + "ms latency."); } - if (waveform.getTimings()[currentIndex] == 0) { + VibrationEffectSegment segment = effect.getSegments().get(currentIndex); + if (!(segment instanceof StepSegment)) { + return nextSteps(); + } + StepSegment stepSegment = (StepSegment) segment; + if (stepSegment.getDuration() == 0) { // Skip waveform entries with zero timing. return nextSteps(); } - int amplitude = waveform.getAmplitudes()[currentIndex]; + float amplitude = stepSegment.getAmplitude(); if (amplitude == 0) { stopVibrating(); return nextSteps(); @@ -771,7 +777,7 @@ final class VibrationThread extends Thread implements IBinder.DeathRecipient { if (startTime >= mNextVibratorStopTime) { // Vibrator has stopped. Turn vibrator back on for the duration of another // cycle before setting the amplitude. - long onDuration = getVibratorOnDuration(waveform, currentIndex); + long onDuration = getVibratorOnDuration(effect, currentIndex); if (onDuration > 0) { startVibrating(onDuration); mNextVibratorStopTime = @@ -806,7 +812,7 @@ final class VibrationThread extends Thread implements IBinder.DeathRecipient { controller.on(duration, mVibration.id); } - private void changeAmplitude(int amplitude) { + private void changeAmplitude(float amplitude) { if (DEBUG) { Slog.d(TAG, "Amplitude changed on vibrator " + controller.getVibratorInfo().getId() + " to " + amplitude); @@ -816,16 +822,16 @@ final class VibrationThread extends Thread implements IBinder.DeathRecipient { @NonNull private List nextSteps() { - long nextStartTime = startTime + waveform.getTimings()[currentIndex]; + long nextStartTime = startTime + effect.getSegments().get(currentIndex).getDuration(); int nextIndex = currentIndex + 1; - if (nextIndex >= waveform.getTimings().length) { - nextIndex = waveform.getRepeatIndex(); + if (nextIndex >= effect.getSegments().size()) { + nextIndex = effect.getRepeatIndex(); } - if (nextIndex < 0) { - return Arrays.asList(new VibratorOffStep(nextStartTime, controller)); - } - return Arrays.asList(new VibratorAmplitudeStep(nextStartTime, controller, waveform, - nextIndex, mNextVibratorStopTime)); + Step nextStep = nextIndex < 0 + ? new VibratorOffStep(nextStartTime, controller) + : new VibratorAmplitudeStep(nextStartTime, controller, effect, nextIndex, + mNextVibratorStopTime); + return Arrays.asList(nextStep); } } @@ -909,13 +915,13 @@ final class VibrationThread extends Thread implements IBinder.DeathRecipient { private long calculateRequiredSyncCapabilities(SparseArray effects) { long prepareCap = 0; for (int i = 0; i < effects.size(); i++) { - VibrationEffect effect = effects.valueAt(i); - if (effect instanceof VibrationEffect.OneShot - || effect instanceof VibrationEffect.Waveform) { + VibrationEffect.Composed composed = (VibrationEffect.Composed) effects.valueAt(i); + VibrationEffectSegment firstSegment = composed.getSegments().get(0); + if (firstSegment instanceof StepSegment) { prepareCap |= IVibratorManager.CAP_PREPARE_ON; - } else if (effect instanceof VibrationEffect.Prebaked) { + } else if (firstSegment instanceof PrebakedSegment) { prepareCap |= IVibratorManager.CAP_PREPARE_PERFORM; - } else if (effect instanceof VibrationEffect.Composed) { + } else if (firstSegment instanceof PrimitiveSegment) { prepareCap |= IVibratorManager.CAP_PREPARE_COMPOSE; } } diff --git a/services/core/java/com/android/server/vibrator/VibratorController.java b/services/core/java/com/android/server/vibrator/VibratorController.java index e3dc70b41c0d5..66200b32ac0c2 100644 --- a/services/core/java/com/android/server/vibrator/VibratorController.java +++ b/services/core/java/com/android/server/vibrator/VibratorController.java @@ -22,8 +22,9 @@ import android.os.Binder; import android.os.IVibratorStateListener; import android.os.RemoteCallbackList; import android.os.RemoteException; -import android.os.VibrationEffect; import android.os.VibratorInfo; +import android.os.vibrator.PrebakedSegment; +import android.os.vibrator.PrimitiveSegment; import android.util.Slog; import com.android.internal.annotations.GuardedBy; @@ -156,21 +157,22 @@ final class VibratorController { * Update the predefined vibration effect saved with given id. This will remove the saved effect * if given {@code effect} is {@code null}. */ - public void updateAlwaysOn(int id, @Nullable VibrationEffect.Prebaked effect) { + public void updateAlwaysOn(int id, @Nullable PrebakedSegment prebaked) { if (!mVibratorInfo.hasCapability(IVibrator.CAP_ALWAYS_ON_CONTROL)) { return; } synchronized (mLock) { - if (effect == null) { + if (prebaked == null) { mNativeWrapper.alwaysOnDisable(id); } else { - mNativeWrapper.alwaysOnEnable(id, effect.getId(), effect.getEffectStrength()); + mNativeWrapper.alwaysOnEnable(id, prebaked.getEffectId(), + prebaked.getEffectStrength()); } } } /** Set the vibration amplitude. This will NOT affect the state of {@link #isVibrating()}. */ - public void setAmplitude(int amplitude) { + public void setAmplitude(float amplitude) { synchronized (mLock) { if (mVibratorInfo.hasCapability(IVibrator.CAP_AMPLITUDE_CONTROL)) { mNativeWrapper.setAmplitude(amplitude); @@ -199,10 +201,10 @@ final class VibratorController { * * @return The duration of the effect playing, or 0 if unsupported. */ - public long on(VibrationEffect.Prebaked effect, long vibrationId) { + public long on(PrebakedSegment prebaked, long vibrationId) { synchronized (mLock) { - long duration = mNativeWrapper.perform(effect.getId(), effect.getEffectStrength(), - vibrationId); + long duration = mNativeWrapper.perform(prebaked.getEffectId(), + prebaked.getEffectStrength(), vibrationId); if (duration > 0) { notifyVibratorOnLocked(); } @@ -211,21 +213,18 @@ final class VibratorController { } /** - * Plays composited vibration effect, using {@code vibrationId} or completion callback to - * {@link OnVibrationCompleteListener}. + * Plays a composition of vibration primitives, using {@code vibrationId} or completion callback + * to {@link OnVibrationCompleteListener}. * *

This will affect the state of {@link #isVibrating()}. * * @return The duration of the effect playing, or 0 if unsupported. */ - public long on(VibrationEffect.Composed effect, long vibrationId) { + public long on(PrimitiveSegment[] primitives, long vibrationId) { if (!mVibratorInfo.hasCapability(IVibrator.CAP_COMPOSE_EFFECTS)) { return 0; } synchronized (mLock) { - VibrationEffect.Composition.PrimitiveEffect[] primitives = - effect.getPrimitiveEffects().toArray( - new VibrationEffect.Composition.PrimitiveEffect[0]); long duration = mNativeWrapper.compose(primitives, vibrationId); if (duration > 0) { notifyVibratorOnLocked(); @@ -313,13 +312,13 @@ final class VibratorController { private static native boolean isAvailable(long nativePtr); private static native void on(long nativePtr, long milliseconds, long vibrationId); private static native void off(long nativePtr); - private static native void setAmplitude(long nativePtr, int amplitude); + private static native void setAmplitude(long nativePtr, float amplitude); private static native int[] getSupportedEffects(long nativePtr); private static native int[] getSupportedPrimitives(long nativePtr); - private static native long performEffect( - long nativePtr, long effect, long strength, long vibrationId); - private static native long performComposedEffect(long nativePtr, - VibrationEffect.Composition.PrimitiveEffect[] effect, long vibrationId); + private static native long performEffect(long nativePtr, long effect, long strength, + long vibrationId); + private static native long performComposedEffect(long nativePtr, PrimitiveSegment[] effect, + long vibrationId); private static native void setExternalControl(long nativePtr, boolean enabled); private static native long getCapabilities(long nativePtr); private static native void alwaysOnEnable(long nativePtr, long id, long effect, @@ -359,7 +358,7 @@ final class VibratorController { } /** Sets the amplitude for the vibrator to run. */ - public void setAmplitude(int amplitude) { + public void setAmplitude(float amplitude) { setAmplitude(mNativePtr, amplitude); } @@ -379,9 +378,8 @@ final class VibratorController { } /** Turns vibrator on to perform one of the supported composed effects. */ - public long compose( - VibrationEffect.Composition.PrimitiveEffect[] effect, long vibrationId) { - return performComposedEffect(mNativePtr, effect, vibrationId); + public long compose(PrimitiveSegment[] primitives, long vibrationId) { + return performComposedEffect(mNativePtr, primitives, vibrationId); } /** Enabled the device vibrator to be controlled by another service. */ diff --git a/services/core/java/com/android/server/vibrator/VibratorManagerService.java b/services/core/java/com/android/server/vibrator/VibratorManagerService.java index c9751bb7abe4c..5fd1d7a6e1dc8 100644 --- a/services/core/java/com/android/server/vibrator/VibratorManagerService.java +++ b/services/core/java/com/android/server/vibrator/VibratorManagerService.java @@ -48,6 +48,8 @@ import android.os.VibrationAttributes; import android.os.VibrationEffect; import android.os.Vibrator; import android.os.VibratorInfo; +import android.os.vibrator.PrebakedSegment; +import android.os.vibrator.VibrationEffectSegment; import android.util.Slog; import android.util.SparseArray; import android.util.proto.ProtoOutputStream; @@ -312,7 +314,7 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { } attrs = fixupVibrationAttributes(attrs); synchronized (mLock) { - SparseArray effects = fixupAlwaysOnEffectsLocked(effect); + SparseArray effects = fixupAlwaysOnEffectsLocked(effect); if (effects == null) { // Invalid effects set in CombinedVibrationEffect, or always-on capability is // missing on individual vibrators. @@ -347,8 +349,7 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { attrs = fixupVibrationAttributes(attrs); Vibration vib = new Vibration(token, mNextVibrationId.getAndIncrement(), effect, attrs, uid, opPkg, reason); - // Update with fixed up effect to keep the original effect in Vibration for debugging. - vib.updateEffect(fixupVibrationEffect(effect)); + fillVibrationFallbacks(vib, effect); synchronized (mLock) { Vibration.Status ignoreStatus = shouldIgnoreVibrationLocked(vib); @@ -476,7 +477,7 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { private void updateAlwaysOnLocked(AlwaysOnVibration vib) { for (int i = 0; i < vib.effects.size(); i++) { VibratorController vibrator = mVibrators.get(vib.effects.keyAt(i)); - VibrationEffect.Prebaked effect = vib.effects.valueAt(i); + PrebakedSegment effect = vib.effects.valueAt(i); if (vibrator == null) { continue; } @@ -496,7 +497,7 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { private Vibration.Status startVibrationLocked(Vibration vib) { Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "startVibrationLocked"); try { - vib.updateEffect(mVibrationScaler.scale(vib.getEffect(), vib.attrs.getUsage())); + vib.updateEffects(effect -> mVibrationScaler.scale(effect, vib.attrs.getUsage())); boolean inputDevicesAvailable = mInputDeviceDelegate.vibrateIfAvailable( vib.uid, vib.opPkg, vib.getEffect(), vib.reason, vib.attrs); if (inputDevicesAvailable) { @@ -757,43 +758,38 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { * Sets fallback effects to all prebaked ones in given combination of effects, based on {@link * VibrationSettings#getFallbackEffect}. */ - private CombinedVibrationEffect fixupVibrationEffect(CombinedVibrationEffect effect) { + private void fillVibrationFallbacks(Vibration vib, CombinedVibrationEffect effect) { if (effect instanceof CombinedVibrationEffect.Mono) { - return CombinedVibrationEffect.createSynced( - fixupVibrationEffect(((CombinedVibrationEffect.Mono) effect).getEffect())); + fillVibrationFallbacks(vib, ((CombinedVibrationEffect.Mono) effect).getEffect()); } else if (effect instanceof CombinedVibrationEffect.Stereo) { - CombinedVibrationEffect.SyncedCombination combination = - CombinedVibrationEffect.startSynced(); SparseArray effects = ((CombinedVibrationEffect.Stereo) effect).getEffects(); for (int i = 0; i < effects.size(); i++) { - combination.addVibrator(effects.keyAt(i), fixupVibrationEffect(effects.valueAt(i))); + fillVibrationFallbacks(vib, effects.valueAt(i)); } - return combination.combine(); } else if (effect instanceof CombinedVibrationEffect.Sequential) { - CombinedVibrationEffect.SequentialCombination combination = - CombinedVibrationEffect.startSequential(); List effects = ((CombinedVibrationEffect.Sequential) effect).getEffects(); - for (CombinedVibrationEffect e : effects) { - combination.addNext(fixupVibrationEffect(e)); + for (int i = 0; i < effects.size(); i++) { + fillVibrationFallbacks(vib, effects.get(i)); } - return combination.combine(); } - return effect; } - private VibrationEffect fixupVibrationEffect(VibrationEffect effect) { - if (effect instanceof VibrationEffect.Prebaked - && ((VibrationEffect.Prebaked) effect).shouldFallback()) { - VibrationEffect.Prebaked prebaked = (VibrationEffect.Prebaked) effect; - VibrationEffect fallback = mVibrationSettings.getFallbackEffect(prebaked.getId()); - if (fallback != null) { - return new VibrationEffect.Prebaked(prebaked.getId(), prebaked.getEffectStrength(), - fallback); + private void fillVibrationFallbacks(Vibration vib, VibrationEffect effect) { + VibrationEffect.Composed composed = (VibrationEffect.Composed) effect; + int segmentCount = composed.getSegments().size(); + for (int i = 0; i < segmentCount; i++) { + VibrationEffectSegment segment = composed.getSegments().get(i); + if (segment instanceof PrebakedSegment) { + PrebakedSegment prebaked = (PrebakedSegment) segment; + VibrationEffect fallback = mVibrationSettings.getFallbackEffect( + prebaked.getEffectId()); + if (prebaked.shouldFallback() && fallback != null) { + vib.addFallback(prebaked.getEffectId(), fallback); + } } } - return effect; } /** @@ -819,7 +815,7 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { @GuardedBy("mLock") @Nullable - private SparseArray fixupAlwaysOnEffectsLocked( + private SparseArray fixupAlwaysOnEffectsLocked( CombinedVibrationEffect effect) { Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "fixupAlwaysOnEffectsLocked"); try { @@ -833,17 +829,17 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { // Only synced combinations can be used for always-on effects. return null; } - SparseArray result = new SparseArray<>(); + SparseArray result = new SparseArray<>(); for (int i = 0; i < effects.size(); i++) { - VibrationEffect prebaked = effects.valueAt(i); - if (!(prebaked instanceof VibrationEffect.Prebaked)) { + PrebakedSegment prebaked = extractPrebakedSegment(effects.valueAt(i)); + if (prebaked == null) { Slog.e(TAG, "Only prebaked effects supported for always-on."); return null; } int vibratorId = effects.keyAt(i); VibratorController vibrator = mVibrators.get(vibratorId); if (vibrator != null && vibrator.hasCapability(IVibrator.CAP_ALWAYS_ON_CONTROL)) { - result.put(vibratorId, (VibrationEffect.Prebaked) prebaked); + result.put(vibratorId, prebaked); } } if (result.size() == 0) { @@ -855,6 +851,20 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { } } + @Nullable + private static PrebakedSegment extractPrebakedSegment(VibrationEffect effect) { + if (effect instanceof VibrationEffect.Composed) { + VibrationEffect.Composed composed = (VibrationEffect.Composed) effect; + if (composed.getSegments().size() == 1) { + VibrationEffectSegment segment = composed.getSegments().get(0); + if (segment instanceof PrebakedSegment) { + return (PrebakedSegment) segment; + } + } + } + return null; + } + /** * Check given mode, one of the AppOpsManager.MODE_*, against {@link VibrationAttributes} to * allow bypassing {@link AppOpsManager} checks. @@ -1008,10 +1018,10 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { public final int uid; public final String opPkg; public final VibrationAttributes attrs; - public final SparseArray effects; + public final SparseArray effects; AlwaysOnVibration(int alwaysOnId, int uid, String opPkg, VibrationAttributes attrs, - SparseArray effects) { + SparseArray effects) { this.alwaysOnId = alwaysOnId; this.uid = uid; this.opPkg = opPkg; diff --git a/services/core/jni/com_android_server_vibrator_VibratorController.cpp b/services/core/jni/com_android_server_vibrator_VibratorController.cpp index f60b35499013c..7f8168af944aa 100644 --- a/services/core/jni/com_android_server_vibrator_VibratorController.cpp +++ b/services/core/jni/com_android_server_vibrator_VibratorController.cpp @@ -170,13 +170,13 @@ static void vibratorOff(JNIEnv* env, jclass /* clazz */, jlong ptr) { wrapper->hal()->off(); } -static void vibratorSetAmplitude(JNIEnv* env, jclass /* clazz */, jlong ptr, jint amplitude) { +static void vibratorSetAmplitude(JNIEnv* env, jclass /* clazz */, jlong ptr, jfloat amplitude) { VibratorControllerWrapper* wrapper = reinterpret_cast(ptr); if (wrapper == nullptr) { ALOGE("vibratorSetAmplitude failed because native wrapper was not initialized"); return; } - wrapper->hal()->setAmplitude(static_cast(amplitude)); + wrapper->hal()->setAmplitude(static_cast(amplitude)); } static void vibratorSetExternalControl(JNIEnv* env, jclass /* clazz */, jlong ptr, @@ -313,9 +313,9 @@ static const JNINativeMethod method_table[] = { {"isAvailable", "(J)Z", (void*)vibratorIsAvailable}, {"on", "(JJJ)V", (void*)vibratorOn}, {"off", "(J)V", (void*)vibratorOff}, - {"setAmplitude", "(JI)V", (void*)vibratorSetAmplitude}, + {"setAmplitude", "(JF)V", (void*)vibratorSetAmplitude}, {"performEffect", "(JJJJ)J", (void*)vibratorPerformEffect}, - {"performComposedEffect", "(J[Landroid/os/VibrationEffect$Composition$PrimitiveEffect;J)J", + {"performComposedEffect", "(J[Landroid/os/vibrator/PrimitiveSegment;J)J", (void*)vibratorPerformComposedEffect}, {"getSupportedEffects", "(J)[I", (void*)vibratorGetSupportedEffects}, {"getSupportedPrimitives", "(J)[I", (void*)vibratorGetSupportedPrimitives}, @@ -334,11 +334,10 @@ int register_android_server_vibrator_VibratorController(JavaVM* jvm, JNIEnv* env jclass listenerClass = FindClassOrDie(env, listenerClassName); sMethodIdOnComplete = GetMethodIDOrDie(env, listenerClass, "onComplete", "(IJ)V"); - jclass primitiveClass = - FindClassOrDie(env, "android/os/VibrationEffect$Composition$PrimitiveEffect"); - sPrimitiveClassInfo.id = GetFieldIDOrDie(env, primitiveClass, "id", "I"); - sPrimitiveClassInfo.scale = GetFieldIDOrDie(env, primitiveClass, "scale", "F"); - sPrimitiveClassInfo.delay = GetFieldIDOrDie(env, primitiveClass, "delay", "I"); + jclass primitiveClass = FindClassOrDie(env, "android/os/vibrator/PrimitiveSegment"); + sPrimitiveClassInfo.id = GetFieldIDOrDie(env, primitiveClass, "mPrimitiveId", "I"); + sPrimitiveClassInfo.scale = GetFieldIDOrDie(env, primitiveClass, "mScale", "F"); + sPrimitiveClassInfo.delay = GetFieldIDOrDie(env, primitiveClass, "mDelay", "I"); return jniRegisterNativeMethods(env, "com/android/server/vibrator/VibratorController$NativeWrapper", diff --git a/services/tests/servicestests/src/com/android/server/vibrator/FakeVibratorControllerProvider.java b/services/tests/servicestests/src/com/android/server/vibrator/FakeVibratorControllerProvider.java index b54b6969e7df9..a959a5e7274d9 100644 --- a/services/tests/servicestests/src/com/android/server/vibrator/FakeVibratorControllerProvider.java +++ b/services/tests/servicestests/src/com/android/server/vibrator/FakeVibratorControllerProvider.java @@ -20,6 +20,10 @@ import android.annotation.Nullable; import android.os.Handler; import android.os.Looper; import android.os.VibrationEffect; +import android.os.vibrator.PrebakedSegment; +import android.os.vibrator.PrimitiveSegment; +import android.os.vibrator.StepSegment; +import android.os.vibrator.VibrationEffectSegment; import com.android.server.vibrator.VibratorController.OnVibrationCompleteListener; @@ -37,9 +41,9 @@ final class FakeVibratorControllerProvider { private static final int EFFECT_DURATION = 20; - private final Map mEnabledAlwaysOnEffects = new HashMap<>(); - private final List mEffects = new ArrayList<>(); - private final List mAmplitudes = new ArrayList<>(); + private final Map mEnabledAlwaysOnEffects = new HashMap<>(); + private final List mEffectSegments = new ArrayList<>(); + private final List mAmplitudes = new ArrayList<>(); private final Handler mHandler; private final FakeNativeWrapper mNativeWrapper; @@ -57,85 +61,96 @@ final class FakeVibratorControllerProvider { public OnVibrationCompleteListener listener; public boolean isInitialized; + @Override public void init(int vibratorId, OnVibrationCompleteListener listener) { isInitialized = true; this.vibratorId = vibratorId; this.listener = listener; } + @Override public boolean isAvailable() { return mIsAvailable; } + @Override public void on(long milliseconds, long vibrationId) { - VibrationEffect effect = VibrationEffect.createOneShot( - milliseconds, VibrationEffect.DEFAULT_AMPLITUDE); - mEffects.add(effect); + mEffectSegments.add( + new StepSegment(VibrationEffect.DEFAULT_AMPLITUDE, (int) milliseconds)); applyLatency(); scheduleListener(milliseconds, vibrationId); } + @Override public void off() { } - public void setAmplitude(int amplitude) { + @Override + public void setAmplitude(float amplitude) { mAmplitudes.add(amplitude); applyLatency(); } + @Override public int[] getSupportedEffects() { return mSupportedEffects; } + @Override public int[] getSupportedPrimitives() { return mSupportedPrimitives; } + @Override public float getResonantFrequency() { return mResonantFrequency; } + @Override public float getQFactor() { return mQFactor; } + @Override public long perform(long effect, long strength, long vibrationId) { if (mSupportedEffects == null || Arrays.binarySearch(mSupportedEffects, (int) effect) < 0) { return 0; } - mEffects.add(new VibrationEffect.Prebaked((int) effect, false, (int) strength)); + mEffectSegments.add(new PrebakedSegment((int) effect, false, (int) strength)); applyLatency(); scheduleListener(EFFECT_DURATION, vibrationId); return EFFECT_DURATION; } - public long compose(VibrationEffect.Composition.PrimitiveEffect[] effect, - long vibrationId) { - VibrationEffect.Composed composed = new VibrationEffect.Composed(Arrays.asList(effect)); - mEffects.add(composed); - applyLatency(); + @Override + public long compose(PrimitiveSegment[] effects, long vibrationId) { long duration = 0; - for (VibrationEffect.Composition.PrimitiveEffect e : effect) { - duration += EFFECT_DURATION + e.delay; + for (PrimitiveSegment primitive : effects) { + duration += EFFECT_DURATION + primitive.getDelay(); + mEffectSegments.add(primitive); } + applyLatency(); scheduleListener(duration, vibrationId); return duration; } + @Override public void setExternalControl(boolean enabled) { } + @Override public long getCapabilities() { return mCapabilities; } + @Override public void alwaysOnEnable(long id, long effect, long strength) { - VibrationEffect.Prebaked prebaked = new VibrationEffect.Prebaked((int) effect, false, - (int) strength); + PrebakedSegment prebaked = new PrebakedSegment((int) effect, false, (int) strength); mEnabledAlwaysOnEffects.put(id, prebaked); } + @Override public void alwaysOnDisable(long id) { mEnabledAlwaysOnEffects.remove(id); } @@ -222,21 +237,21 @@ final class FakeVibratorControllerProvider { * Return the amplitudes set by this controller, including zeroes for each time the vibrator was * turned off. */ - public List getAmplitudes() { + public List getAmplitudes() { return new ArrayList<>(mAmplitudes); } - /** Return list of {@link VibrationEffect} played by this controller, in order. */ - public List getEffects() { - return new ArrayList<>(mEffects); + /** Return list of {@link VibrationEffectSegment} played by this controller, in order. */ + public List getEffectSegments() { + return new ArrayList<>(mEffectSegments); } /** - * Return the {@link VibrationEffect.Prebaked} effect enabled with given id, or {@code null} if + * Return the {@link PrebakedSegment} effect enabled with given id, or {@code null} if * missing or disabled. */ @Nullable - public VibrationEffect.Prebaked getAlwaysOnEffect(int id) { + public PrebakedSegment getAlwaysOnEffect(int id) { return mEnabledAlwaysOnEffects.get((long) id); } } diff --git a/services/tests/servicestests/src/com/android/server/vibrator/VibrationScalerTest.java b/services/tests/servicestests/src/com/android/server/vibrator/VibrationScalerTest.java index b6c11fe62ff60..59c0b0e96fcde 100644 --- a/services/tests/servicestests/src/com/android/server/vibrator/VibrationScalerTest.java +++ b/services/tests/servicestests/src/com/android/server/vibrator/VibrationScalerTest.java @@ -26,7 +26,6 @@ import static org.mockito.Mockito.when; import android.content.ContentResolver; import android.content.Context; import android.content.ContextWrapper; -import android.os.CombinedVibrationEffect; import android.os.Handler; import android.os.IExternalVibratorService; import android.os.PowerManagerInternal; @@ -35,6 +34,10 @@ import android.os.VibrationAttributes; import android.os.VibrationEffect; import android.os.Vibrator; import android.os.test.TestLooper; +import android.os.vibrator.PrebakedSegment; +import android.os.vibrator.PrimitiveSegment; +import android.os.vibrator.StepSegment; +import android.os.vibrator.VibrationEffectSegment; import android.platform.test.annotations.Presubmit; import android.provider.Settings; @@ -130,51 +133,13 @@ public class VibrationScalerTest { } @Test - public void scale_withCombined_resolvesAndScalesRecursively() { + public void scale_withPrebakedSegment_setsEffectStrengthBasedOnSettings() { setUserSetting(Settings.System.NOTIFICATION_VIBRATION_INTENSITY, Vibrator.VIBRATION_INTENSITY_HIGH); - VibrationEffect prebaked = VibrationEffect.createPredefined(VibrationEffect.EFFECT_CLICK); - VibrationEffect oneShot = VibrationEffect.createOneShot(10, 10); + PrebakedSegment effect = new PrebakedSegment(VibrationEffect.EFFECT_CLICK, + /* shouldFallback= */ false, VibrationEffect.EFFECT_STRENGTH_MEDIUM); - CombinedVibrationEffect.Mono monoScaled = mVibrationScaler.scale( - CombinedVibrationEffect.createSynced(prebaked), - VibrationAttributes.USAGE_NOTIFICATION); - VibrationEffect.Prebaked prebakedScaled = (VibrationEffect.Prebaked) monoScaled.getEffect(); - assertEquals(prebakedScaled.getEffectStrength(), VibrationEffect.EFFECT_STRENGTH_STRONG); - - CombinedVibrationEffect.Stereo stereoScaled = mVibrationScaler.scale( - CombinedVibrationEffect.startSynced() - .addVibrator(1, prebaked) - .addVibrator(2, oneShot) - .combine(), - VibrationAttributes.USAGE_NOTIFICATION); - prebakedScaled = (VibrationEffect.Prebaked) stereoScaled.getEffects().get(1); - assertEquals(prebakedScaled.getEffectStrength(), VibrationEffect.EFFECT_STRENGTH_STRONG); - VibrationEffect.OneShot oneshotScaled = - (VibrationEffect.OneShot) stereoScaled.getEffects().get(2); - assertTrue(oneshotScaled.getAmplitude() > 0); - - CombinedVibrationEffect.Sequential sequentialScaled = mVibrationScaler.scale( - CombinedVibrationEffect.startSequential() - .addNext(CombinedVibrationEffect.createSynced(prebaked)) - .addNext(CombinedVibrationEffect.createSynced(oneShot)) - .combine(), - VibrationAttributes.USAGE_NOTIFICATION); - monoScaled = (CombinedVibrationEffect.Mono) sequentialScaled.getEffects().get(0); - prebakedScaled = (VibrationEffect.Prebaked) monoScaled.getEffect(); - assertEquals(prebakedScaled.getEffectStrength(), VibrationEffect.EFFECT_STRENGTH_STRONG); - monoScaled = (CombinedVibrationEffect.Mono) sequentialScaled.getEffects().get(1); - oneshotScaled = (VibrationEffect.OneShot) monoScaled.getEffect(); - assertTrue(oneshotScaled.getAmplitude() > 0); - } - - @Test - public void scale_withPrebaked_setsEffectStrengthBasedOnSettings() { - setUserSetting(Settings.System.NOTIFICATION_VIBRATION_INTENSITY, - Vibrator.VIBRATION_INTENSITY_HIGH); - VibrationEffect effect = VibrationEffect.createPredefined(VibrationEffect.EFFECT_CLICK); - - VibrationEffect.Prebaked scaled = mVibrationScaler.scale( + PrebakedSegment scaled = mVibrationScaler.scale( effect, VibrationAttributes.USAGE_NOTIFICATION); assertEquals(scaled.getEffectStrength(), VibrationEffect.EFFECT_STRENGTH_STRONG); @@ -196,25 +161,33 @@ public class VibrationScalerTest { } @Test - public void scale_withPrebakedAndFallback_resolvesAndScalesRecursively() { + public void scale_withPrebakedEffect_setsEffectStrengthBasedOnSettings() { setUserSetting(Settings.System.NOTIFICATION_VIBRATION_INTENSITY, Vibrator.VIBRATION_INTENSITY_HIGH); - VibrationEffect.OneShot fallback2 = (VibrationEffect.OneShot) VibrationEffect.createOneShot( - 10, VibrationEffect.DEFAULT_AMPLITUDE); - VibrationEffect.Prebaked fallback1 = new VibrationEffect.Prebaked( - VibrationEffect.EFFECT_TICK, VibrationEffect.EFFECT_STRENGTH_MEDIUM, fallback2); - VibrationEffect.Prebaked effect = new VibrationEffect.Prebaked(VibrationEffect.EFFECT_CLICK, - VibrationEffect.EFFECT_STRENGTH_MEDIUM, fallback1); + VibrationEffect effect = VibrationEffect.createPredefined(VibrationEffect.EFFECT_CLICK); - VibrationEffect.Prebaked scaled = mVibrationScaler.scale( - effect, VibrationAttributes.USAGE_NOTIFICATION); - VibrationEffect.Prebaked scaledFallback1 = - (VibrationEffect.Prebaked) scaled.getFallbackEffect(); - VibrationEffect.OneShot scaledFallback2 = - (VibrationEffect.OneShot) scaledFallback1.getFallbackEffect(); + PrebakedSegment scaled = getFirstSegment(mVibrationScaler.scale( + effect, VibrationAttributes.USAGE_NOTIFICATION)); + assertEquals(scaled.getEffectStrength(), VibrationEffect.EFFECT_STRENGTH_STRONG); + + setUserSetting(Settings.System.NOTIFICATION_VIBRATION_INTENSITY, + Vibrator.VIBRATION_INTENSITY_MEDIUM); + scaled = getFirstSegment(mVibrationScaler.scale( + effect, VibrationAttributes.USAGE_NOTIFICATION)); + assertEquals(scaled.getEffectStrength(), VibrationEffect.EFFECT_STRENGTH_MEDIUM); + + setUserSetting(Settings.System.NOTIFICATION_VIBRATION_INTENSITY, + Vibrator.VIBRATION_INTENSITY_LOW); + scaled = getFirstSegment(mVibrationScaler.scale( + effect, VibrationAttributes.USAGE_NOTIFICATION)); + assertEquals(scaled.getEffectStrength(), VibrationEffect.EFFECT_STRENGTH_LIGHT); + + setUserSetting(Settings.System.NOTIFICATION_VIBRATION_INTENSITY, + Vibrator.VIBRATION_INTENSITY_OFF); + scaled = getFirstSegment(mVibrationScaler.scale( + effect, VibrationAttributes.USAGE_NOTIFICATION)); + // Unexpected intensity setting will be mapped to STRONG. assertEquals(scaled.getEffectStrength(), VibrationEffect.EFFECT_STRENGTH_STRONG); - assertEquals(scaledFallback1.getEffectStrength(), VibrationEffect.EFFECT_STRENGTH_STRONG); - assertTrue(scaledFallback2.getAmplitude() > 0); } @Test @@ -224,20 +197,20 @@ public class VibrationScalerTest { setUserSetting(Settings.System.RING_VIBRATION_INTENSITY, Vibrator.VIBRATION_INTENSITY_LOW); - VibrationEffect.OneShot oneShot = mVibrationScaler.scale( + StepSegment resolved = getFirstSegment(mVibrationScaler.scale( VibrationEffect.createOneShot(10, VibrationEffect.DEFAULT_AMPLITUDE), - VibrationAttributes.USAGE_RINGTONE); - assertTrue(oneShot.getAmplitude() > 0); + VibrationAttributes.USAGE_RINGTONE)); + assertTrue(resolved.getAmplitude() > 0); - VibrationEffect.Waveform waveform = mVibrationScaler.scale( + resolved = getFirstSegment(mVibrationScaler.scale( VibrationEffect.createWaveform(new long[]{10}, new int[]{VibrationEffect.DEFAULT_AMPLITUDE}, -1), - VibrationAttributes.USAGE_RINGTONE); - assertTrue(waveform.getAmplitudes()[0] > 0); + VibrationAttributes.USAGE_RINGTONE)); + assertTrue(resolved.getAmplitude() > 0); } @Test - public void scale_withOneShotWaveform_scalesAmplitude() { + public void scale_withOneShotAndWaveform_scalesAmplitude() { mFakeVibrator.setDefaultRingVibrationIntensity(Vibrator.VIBRATION_INTENSITY_LOW); setUserSetting(Settings.System.RING_VIBRATION_INTENSITY, Vibrator.VIBRATION_INTENSITY_HIGH); @@ -248,21 +221,21 @@ public class VibrationScalerTest { setUserSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY, Vibrator.VIBRATION_INTENSITY_MEDIUM); - VibrationEffect.OneShot oneShot = mVibrationScaler.scale( - VibrationEffect.createOneShot(100, 100), VibrationAttributes.USAGE_RINGTONE); + StepSegment scaled = getFirstSegment(mVibrationScaler.scale( + VibrationEffect.createOneShot(128, 128), VibrationAttributes.USAGE_RINGTONE)); // Ringtone scales up. - assertTrue(oneShot.getAmplitude() > 100); + assertTrue(scaled.getAmplitude() > 0.5); - VibrationEffect.Waveform waveform = mVibrationScaler.scale( - VibrationEffect.createWaveform(new long[]{100}, new int[]{100}, -1), - VibrationAttributes.USAGE_NOTIFICATION); + scaled = getFirstSegment(mVibrationScaler.scale( + VibrationEffect.createWaveform(new long[]{128}, new int[]{128}, -1), + VibrationAttributes.USAGE_NOTIFICATION)); // Notification scales down. - assertTrue(waveform.getAmplitudes()[0] < 100); + assertTrue(scaled.getAmplitude() < 0.5); - oneShot = mVibrationScaler.scale(VibrationEffect.createOneShot(100, 100), - VibrationAttributes.USAGE_TOUCH); + scaled = getFirstSegment(mVibrationScaler.scale(VibrationEffect.createOneShot(128, 128), + VibrationAttributes.USAGE_TOUCH)); // Haptic feedback does not scale. - assertEquals(100, oneShot.getAmplitude()); + assertEquals(128f / 255, scaled.getAmplitude(), 1e-5); } @Test @@ -280,18 +253,23 @@ public class VibrationScalerTest { VibrationEffect composed = VibrationEffect.startComposition() .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 0.5f).compose(); - VibrationEffect.Composed scaled = mVibrationScaler.scale(composed, - VibrationAttributes.USAGE_RINGTONE); + PrimitiveSegment scaled = getFirstSegment(mVibrationScaler.scale(composed, + VibrationAttributes.USAGE_RINGTONE)); // Ringtone scales up. - assertTrue(scaled.getPrimitiveEffects().get(0).scale > 0.5f); + assertTrue(scaled.getScale() > 0.5f); - scaled = mVibrationScaler.scale(composed, VibrationAttributes.USAGE_NOTIFICATION); + scaled = getFirstSegment(mVibrationScaler.scale(composed, + VibrationAttributes.USAGE_NOTIFICATION)); // Notification scales down. - assertTrue(scaled.getPrimitiveEffects().get(0).scale < 0.5f); + assertTrue(scaled.getScale() < 0.5f); - scaled = mVibrationScaler.scale(composed, VibrationAttributes.USAGE_TOUCH); + scaled = getFirstSegment(mVibrationScaler.scale(composed, VibrationAttributes.USAGE_TOUCH)); // Haptic feedback does not scale. - assertEquals(0.5, scaled.getPrimitiveEffects().get(0).scale, 1e-5); + assertEquals(0.5, scaled.getScale(), 1e-5); + } + + private T getFirstSegment(VibrationEffect.Composed effect) { + return (T) effect.getSegments().get(0); } private void setUserSetting(String settingName, int value) { diff --git a/services/tests/servicestests/src/com/android/server/vibrator/VibrationThreadTest.java b/services/tests/servicestests/src/com/android/server/vibrator/VibrationThreadTest.java index 7d5eec0834a17..739a1a3c92f23 100644 --- a/services/tests/servicestests/src/com/android/server/vibrator/VibrationThreadTest.java +++ b/services/tests/servicestests/src/com/android/server/vibrator/VibrationThreadTest.java @@ -40,6 +40,10 @@ import android.os.SystemClock; import android.os.VibrationAttributes; import android.os.VibrationEffect; import android.os.test.TestLooper; +import android.os.vibrator.PrebakedSegment; +import android.os.vibrator.PrimitiveSegment; +import android.os.vibrator.StepSegment; +import android.os.vibrator.VibrationEffectSegment; import android.platform.test.annotations.LargeTest; import android.platform.test.annotations.Presubmit; import android.util.SparseArray; @@ -61,6 +65,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.function.Predicate; +import java.util.stream.Collectors; /** * Tests for {@link VibrationThread}. @@ -142,8 +147,8 @@ public class VibrationThreadTest { assertFalse(thread.getVibrators().get(VIBRATOR_ID).isVibrating()); assertEquals(Arrays.asList(expectedOneShot(10)), - mVibratorProviders.get(VIBRATOR_ID).getEffects()); - assertEquals(Arrays.asList(100), mVibratorProviders.get(VIBRATOR_ID).getAmplitudes()); + mVibratorProviders.get(VIBRATOR_ID).getEffectSegments()); + assertEquals(expectedAmplitudes(100), mVibratorProviders.get(VIBRATOR_ID).getAmplitudes()); } @Test @@ -161,7 +166,7 @@ public class VibrationThreadTest { assertFalse(thread.getVibrators().get(VIBRATOR_ID).isVibrating()); assertEquals(Arrays.asList(expectedOneShot(10)), - mVibratorProviders.get(VIBRATOR_ID).getEffects()); + mVibratorProviders.get(VIBRATOR_ID).getEffectSegments()); assertTrue(mVibratorProviders.get(VIBRATOR_ID).getAmplitudes().isEmpty()); } @@ -183,8 +188,9 @@ public class VibrationThreadTest { assertFalse(thread.getVibrators().get(VIBRATOR_ID).isVibrating()); assertEquals(Arrays.asList(expectedOneShot(15)), - mVibratorProviders.get(VIBRATOR_ID).getEffects()); - assertEquals(Arrays.asList(1, 2, 3), mVibratorProviders.get(VIBRATOR_ID).getAmplitudes()); + mVibratorProviders.get(VIBRATOR_ID).getEffectSegments()); + assertEquals(expectedAmplitudes(1, 2, 3), + mVibratorProviders.get(VIBRATOR_ID).getAmplitudes()); } @Test @@ -213,12 +219,12 @@ public class VibrationThreadTest { verify(mThreadCallbacks).onVibrationEnded(eq(vibrationId), eq(Vibration.Status.CANCELLED)); assertFalse(thread.getVibrators().get(VIBRATOR_ID).isVibrating()); - List playedAmplitudes = fakeVibrator.getAmplitudes(); - assertFalse(fakeVibrator.getEffects().isEmpty()); + List playedAmplitudes = fakeVibrator.getAmplitudes(); + assertFalse(fakeVibrator.getEffectSegments().isEmpty()); assertFalse(playedAmplitudes.isEmpty()); for (int i = 0; i < playedAmplitudes.size(); i++) { - assertEquals(amplitudes[i % amplitudes.length], playedAmplitudes.get(i).intValue()); + assertEquals(amplitudes[i % amplitudes.length] / 255f, playedAmplitudes.get(i), 1e-5); } } @@ -292,7 +298,7 @@ public class VibrationThreadTest { assertFalse(thread.getVibrators().get(VIBRATOR_ID).isVibrating()); assertEquals(Arrays.asList(expectedPrebaked(VibrationEffect.EFFECT_THUD)), - mVibratorProviders.get(VIBRATOR_ID).getEffects()); + mVibratorProviders.get(VIBRATOR_ID).getEffectSegments()); } @Test @@ -302,9 +308,10 @@ public class VibrationThreadTest { long vibrationId = 1; VibrationEffect fallback = VibrationEffect.createOneShot(10, 100); - VibrationEffect.Prebaked effect = new VibrationEffect.Prebaked(VibrationEffect.EFFECT_CLICK, - VibrationEffect.EFFECT_STRENGTH_STRONG, fallback); - VibrationThread thread = startThreadAndDispatcher(vibrationId, effect); + Vibration vibration = createVibration(vibrationId, CombinedVibrationEffect.createSynced( + VibrationEffect.get(VibrationEffect.EFFECT_CLICK))); + vibration.addFallback(VibrationEffect.EFFECT_CLICK, fallback); + VibrationThread thread = startThreadAndDispatcher(vibration); waitForCompletion(thread); verify(mIBatteryStatsMock).noteVibratorOn(eq(UID), eq(10L)); @@ -314,8 +321,8 @@ public class VibrationThreadTest { assertFalse(thread.getVibrators().get(VIBRATOR_ID).isVibrating()); assertEquals(Arrays.asList(expectedOneShot(10)), - mVibratorProviders.get(VIBRATOR_ID).getEffects()); - assertEquals(Arrays.asList(100), mVibratorProviders.get(VIBRATOR_ID).getAmplitudes()); + mVibratorProviders.get(VIBRATOR_ID).getEffectSegments()); + assertEquals(expectedAmplitudes(100), mVibratorProviders.get(VIBRATOR_ID).getAmplitudes()); } @Test @@ -331,7 +338,7 @@ public class VibrationThreadTest { verify(mControllerCallbacks, never()).onComplete(eq(VIBRATOR_ID), eq(vibrationId)); verify(mThreadCallbacks).onVibrationEnded(eq(vibrationId), eq(Vibration.Status.IGNORED_UNSUPPORTED)); - assertTrue(mVibratorProviders.get(VIBRATOR_ID).getEffects().isEmpty()); + assertTrue(mVibratorProviders.get(VIBRATOR_ID).getEffectSegments().isEmpty()); } @Test @@ -351,7 +358,10 @@ public class VibrationThreadTest { verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibrationId)); verify(mThreadCallbacks).onVibrationEnded(eq(vibrationId), eq(Vibration.Status.FINISHED)); assertFalse(thread.getVibrators().get(VIBRATOR_ID).isVibrating()); - assertEquals(Arrays.asList(effect), mVibratorProviders.get(VIBRATOR_ID).getEffects()); + assertEquals(Arrays.asList( + expectedPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1, 0), + expectedPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK, 0.5f, 0)), + mVibratorProviders.get(VIBRATOR_ID).getEffectSegments()); } @Test @@ -368,7 +378,7 @@ public class VibrationThreadTest { verify(mControllerCallbacks, never()).onComplete(eq(VIBRATOR_ID), eq(vibrationId)); verify(mThreadCallbacks).onVibrationEnded(eq(vibrationId), eq(Vibration.Status.IGNORED_UNSUPPORTED)); - assertTrue(mVibratorProviders.get(VIBRATOR_ID).getEffects().isEmpty()); + assertTrue(mVibratorProviders.get(VIBRATOR_ID).getEffectSegments().isEmpty()); } @Test @@ -428,7 +438,7 @@ public class VibrationThreadTest { assertFalse(thread.getVibrators().get(VIBRATOR_ID).isVibrating()); assertEquals(Arrays.asList(expectedPrebaked(VibrationEffect.EFFECT_TICK)), - mVibratorProviders.get(VIBRATOR_ID).getEffects()); + mVibratorProviders.get(VIBRATOR_ID).getEffectSegments()); } @Test @@ -454,10 +464,10 @@ public class VibrationThreadTest { assertFalse(thread.getVibrators().get(2).isVibrating()); assertFalse(thread.getVibrators().get(3).isVibrating()); - VibrationEffect expected = expectedPrebaked(VibrationEffect.EFFECT_CLICK); - assertEquals(Arrays.asList(expected), mVibratorProviders.get(1).getEffects()); - assertEquals(Arrays.asList(expected), mVibratorProviders.get(2).getEffects()); - assertEquals(Arrays.asList(expected), mVibratorProviders.get(3).getEffects()); + VibrationEffectSegment expected = expectedPrebaked(VibrationEffect.EFFECT_CLICK); + assertEquals(Arrays.asList(expected), mVibratorProviders.get(1).getEffectSegments()); + assertEquals(Arrays.asList(expected), mVibratorProviders.get(2).getEffectSegments()); + assertEquals(Arrays.asList(expected), mVibratorProviders.get(3).getEffectSegments()); } @Test @@ -495,12 +505,16 @@ public class VibrationThreadTest { assertFalse(thread.getVibrators().get(4).isVibrating()); assertEquals(Arrays.asList(expectedPrebaked(VibrationEffect.EFFECT_CLICK)), - mVibratorProviders.get(1).getEffects()); - assertEquals(Arrays.asList(expectedOneShot(10)), mVibratorProviders.get(2).getEffects()); - assertEquals(Arrays.asList(100), mVibratorProviders.get(2).getAmplitudes()); - assertEquals(Arrays.asList(expectedOneShot(20)), mVibratorProviders.get(3).getEffects()); - assertEquals(Arrays.asList(1, 2), mVibratorProviders.get(3).getAmplitudes()); - assertEquals(Arrays.asList(composed), mVibratorProviders.get(4).getEffects()); + mVibratorProviders.get(1).getEffectSegments()); + assertEquals(Arrays.asList(expectedOneShot(10)), + mVibratorProviders.get(2).getEffectSegments()); + assertEquals(expectedAmplitudes(100), mVibratorProviders.get(2).getAmplitudes()); + assertEquals(Arrays.asList(expectedOneShot(20)), + mVibratorProviders.get(3).getEffectSegments()); + assertEquals(expectedAmplitudes(1, 2), mVibratorProviders.get(3).getAmplitudes()); + assertEquals(Arrays.asList( + expectedPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1, 0)), + mVibratorProviders.get(4).getEffectSegments()); } @Test @@ -540,11 +554,14 @@ public class VibrationThreadTest { assertFalse(thread.getVibrators().get(2).isVibrating()); assertFalse(thread.getVibrators().get(3).isVibrating()); - assertEquals(Arrays.asList(expectedOneShot(10)), mVibratorProviders.get(1).getEffects()); - assertEquals(Arrays.asList(100), mVibratorProviders.get(1).getAmplitudes()); - assertEquals(Arrays.asList(composed), mVibratorProviders.get(2).getEffects()); + assertEquals(Arrays.asList(expectedOneShot(10)), + mVibratorProviders.get(1).getEffectSegments()); + assertEquals(expectedAmplitudes(100), mVibratorProviders.get(1).getAmplitudes()); + assertEquals(Arrays.asList( + expectedPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1, 0)), + mVibratorProviders.get(2).getEffectSegments()); assertEquals(Arrays.asList(expectedPrebaked(VibrationEffect.EFFECT_CLICK)), - mVibratorProviders.get(3).getEffects()); + mVibratorProviders.get(3).getEffectSegments()); } @Test @@ -563,8 +580,9 @@ public class VibrationThreadTest { CombinedVibrationEffect effect = CombinedVibrationEffect.createSynced(composed); VibrationThread thread = startThreadAndDispatcher(vibrationId, effect); - assertTrue(waitUntil(t -> !mVibratorProviders.get(1).getEffects().isEmpty() - && !mVibratorProviders.get(2).getEffects().isEmpty(), thread, TEST_TIMEOUT_MILLIS)); + assertTrue(waitUntil(t -> !mVibratorProviders.get(1).getEffectSegments().isEmpty() + && !mVibratorProviders.get(2).getEffectSegments().isEmpty(), thread, + TEST_TIMEOUT_MILLIS)); thread.syncedVibrationComplete(); waitForCompletion(thread); @@ -574,8 +592,10 @@ public class VibrationThreadTest { verify(mThreadCallbacks, never()).cancelSyncedVibration(); verify(mThreadCallbacks).onVibrationEnded(eq(vibrationId), eq(Vibration.Status.FINISHED)); - assertEquals(Arrays.asList(composed), mVibratorProviders.get(1).getEffects()); - assertEquals(Arrays.asList(composed), mVibratorProviders.get(2).getEffects()); + VibrationEffectSegment expected = expectedPrimitive( + VibrationEffect.Composition.PRIMITIVE_CLICK, 1, 100); + assertEquals(Arrays.asList(expected), mVibratorProviders.get(1).getEffectSegments()); + assertEquals(Arrays.asList(expected), mVibratorProviders.get(2).getEffectSegments()); } @Test @@ -634,10 +654,12 @@ public class VibrationThreadTest { verify(mThreadCallbacks, never()).triggerSyncedVibration(eq(vibrationId)); verify(mThreadCallbacks, never()).cancelSyncedVibration(); - assertEquals(Arrays.asList(expectedOneShot(10)), mVibratorProviders.get(1).getEffects()); - assertEquals(Arrays.asList(100), mVibratorProviders.get(1).getAmplitudes()); - assertEquals(Arrays.asList(expectedOneShot(5)), mVibratorProviders.get(2).getEffects()); - assertEquals(Arrays.asList(200), mVibratorProviders.get(2).getAmplitudes()); + assertEquals(Arrays.asList(expectedOneShot(10)), + mVibratorProviders.get(1).getEffectSegments()); + assertEquals(expectedAmplitudes(100), mVibratorProviders.get(1).getAmplitudes()); + assertEquals(Arrays.asList(expectedOneShot(5)), + mVibratorProviders.get(2).getEffectSegments()); + assertEquals(expectedAmplitudes(200), mVibratorProviders.get(2).getAmplitudes()); } @Test @@ -704,12 +726,15 @@ public class VibrationThreadTest { assertFalse(thread.getVibrators().get(2).isVibrating()); assertFalse(thread.getVibrators().get(3).isVibrating()); - assertEquals(Arrays.asList(expectedOneShot(25)), mVibratorProviders.get(1).getEffects()); - assertEquals(Arrays.asList(expectedOneShot(80)), mVibratorProviders.get(2).getEffects()); - assertEquals(Arrays.asList(expectedOneShot(60)), mVibratorProviders.get(3).getEffects()); - assertEquals(Arrays.asList(1, 2, 3), mVibratorProviders.get(1).getAmplitudes()); - assertEquals(Arrays.asList(4, 5), mVibratorProviders.get(2).getAmplitudes()); - assertEquals(Arrays.asList(6), mVibratorProviders.get(3).getAmplitudes()); + assertEquals(Arrays.asList(expectedOneShot(25)), + mVibratorProviders.get(1).getEffectSegments()); + assertEquals(Arrays.asList(expectedOneShot(80)), + mVibratorProviders.get(2).getEffectSegments()); + assertEquals(Arrays.asList(expectedOneShot(60)), + mVibratorProviders.get(3).getEffectSegments()); + assertEquals(expectedAmplitudes(1, 2, 3), mVibratorProviders.get(1).getAmplitudes()); + assertEquals(expectedAmplitudes(4, 5), mVibratorProviders.get(2).getAmplitudes()); + assertEquals(expectedAmplitudes(6), mVibratorProviders.get(3).getAmplitudes()); } @LargeTest @@ -826,7 +851,7 @@ public class VibrationThreadTest { verify(mVibrationToken).linkToDeath(same(thread), eq(0)); verify(mVibrationToken).unlinkToDeath(same(thread), eq(0)); verify(mThreadCallbacks).onVibrationEnded(eq(vibrationId), eq(Vibration.Status.CANCELLED)); - assertFalse(mVibratorProviders.get(VIBRATOR_ID).getEffects().isEmpty()); + assertFalse(mVibratorProviders.get(VIBRATOR_ID).getEffectSegments().isEmpty()); assertFalse(thread.getVibrators().get(VIBRATOR_ID).isVibrating()); } @@ -843,12 +868,16 @@ public class VibrationThreadTest { private VibrationThread startThreadAndDispatcher(long vibrationId, CombinedVibrationEffect effect) { - VibrationThread thread = new VibrationThread(createVibration(vibrationId, effect), - createVibratorControllers(), mWakeLock, mIBatteryStatsMock, mThreadCallbacks); + return startThreadAndDispatcher(createVibration(vibrationId, effect)); + } + + private VibrationThread startThreadAndDispatcher(Vibration vib) { + VibrationThread thread = new VibrationThread(vib, createVibratorControllers(), mWakeLock, + mIBatteryStatsMock, mThreadCallbacks); doAnswer(answer -> { thread.vibratorComplete(answer.getArgument(0)); return null; - }).when(mControllerCallbacks).onComplete(anyInt(), eq(vibrationId)); + }).when(mControllerCallbacks).onComplete(anyInt(), eq(vib.id)); mTestLooper.startAutoDispatch(); thread.start(); return thread; @@ -891,12 +920,21 @@ public class VibrationThreadTest { return array; } - private VibrationEffect expectedOneShot(long millis) { - return VibrationEffect.createOneShot(millis, VibrationEffect.DEFAULT_AMPLITUDE); + private VibrationEffectSegment expectedOneShot(long millis) { + return new StepSegment(VibrationEffect.DEFAULT_AMPLITUDE, (int) millis); } - private VibrationEffect expectedPrebaked(int effectId) { - return new VibrationEffect.Prebaked(effectId, false, - VibrationEffect.EFFECT_STRENGTH_MEDIUM); + private VibrationEffectSegment expectedPrebaked(int effectId) { + return new PrebakedSegment(effectId, false, VibrationEffect.EFFECT_STRENGTH_MEDIUM); + } + + private VibrationEffectSegment expectedPrimitive(int primitiveId, float scale, int delay) { + return new PrimitiveSegment(primitiveId, scale, delay); + } + + private List expectedAmplitudes(int... amplitudes) { + return Arrays.stream(amplitudes) + .mapToObj(amplitude -> amplitude / 255f) + .collect(Collectors.toList()); } } diff --git a/services/tests/servicestests/src/com/android/server/vibrator/VibratorControllerTest.java b/services/tests/servicestests/src/com/android/server/vibrator/VibratorControllerTest.java index bad3e4c2ed923..0ba3a21b96ea6 100644 --- a/services/tests/servicestests/src/com/android/server/vibrator/VibratorControllerTest.java +++ b/services/tests/servicestests/src/com/android/server/vibrator/VibratorControllerTest.java @@ -39,6 +39,8 @@ import android.os.IBinder; import android.os.IVibratorStateListener; import android.os.VibrationEffect; import android.os.test.TestLooper; +import android.os.vibrator.PrebakedSegment; +import android.os.vibrator.PrimitiveSegment; import android.platform.test.annotations.Presubmit; import androidx.test.InstrumentationRegistry; @@ -49,7 +51,6 @@ import com.android.internal.util.test.FakeSettingsProviderRule; import org.junit.Before; import org.junit.Rule; import org.junit.Test; -import org.mockito.ArgumentCaptor; import org.mockito.InOrder; import org.mockito.Mock; import org.mockito.Mockito; @@ -161,9 +162,9 @@ public class VibratorControllerTest { @Test public void updateAlwaysOn_withCapability_enablesAlwaysOnEffect() { mockVibratorCapabilities(IVibrator.CAP_ALWAYS_ON_CONTROL); - VibrationEffect.Prebaked effect = (VibrationEffect.Prebaked) - VibrationEffect.createPredefined(VibrationEffect.EFFECT_CLICK); - createController().updateAlwaysOn(1, effect); + PrebakedSegment prebaked = createPrebaked(VibrationEffect.EFFECT_CLICK, + VibrationEffect.EFFECT_STRENGTH_MEDIUM); + createController().updateAlwaysOn(1, prebaked); verify(mNativeWrapperMock).alwaysOnEnable( eq(1L), eq((long) VibrationEffect.EFFECT_CLICK), @@ -179,9 +180,9 @@ public class VibratorControllerTest { @Test public void updateAlwaysOn_withoutCapability_ignoresEffect() { - VibrationEffect.Prebaked effect = (VibrationEffect.Prebaked) - VibrationEffect.createPredefined(VibrationEffect.EFFECT_CLICK); - createController().updateAlwaysOn(1, effect); + PrebakedSegment prebaked = createPrebaked(VibrationEffect.EFFECT_CLICK, + VibrationEffect.EFFECT_STRENGTH_MEDIUM); + createController().updateAlwaysOn(1, prebaked); verify(mNativeWrapperMock, never()).alwaysOnDisable(anyLong()); verify(mNativeWrapperMock, never()).alwaysOnEnable(anyLong(), anyLong(), anyLong()); @@ -201,9 +202,9 @@ public class VibratorControllerTest { when(mNativeWrapperMock.perform(anyLong(), anyLong(), anyLong())).thenReturn(10L); VibratorController controller = createController(); - VibrationEffect.Prebaked effect = (VibrationEffect.Prebaked) - VibrationEffect.createPredefined(VibrationEffect.EFFECT_CLICK); - assertEquals(10L, controller.on(effect, 11)); + PrebakedSegment prebaked = createPrebaked(VibrationEffect.EFFECT_CLICK, + VibrationEffect.EFFECT_STRENGTH_MEDIUM); + assertEquals(10L, controller.on(prebaked, 11)); assertTrue(controller.isVibrating()); verify(mNativeWrapperMock).perform(eq((long) VibrationEffect.EFFECT_CLICK), @@ -216,24 +217,13 @@ public class VibratorControllerTest { when(mNativeWrapperMock.compose(any(), anyLong())).thenReturn(15L); VibratorController controller = createController(); - VibrationEffect.Composed effect = (VibrationEffect.Composed) - VibrationEffect.startComposition() - .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 0.5f, 10) - .compose(); - assertEquals(15L, controller.on(effect, 12)); - - ArgumentCaptor primitivesCaptor = - ArgumentCaptor.forClass(VibrationEffect.Composition.PrimitiveEffect[].class); + PrimitiveSegment[] primitives = new PrimitiveSegment[]{ + new PrimitiveSegment(VibrationEffect.Composition.PRIMITIVE_CLICK, 0.5f, 10) + }; + assertEquals(15L, controller.on(primitives, 12)); assertTrue(controller.isVibrating()); - verify(mNativeWrapperMock).compose(primitivesCaptor.capture(), eq(12L)); - - // Check all primitive effect fields are passed down to the HAL. - assertEquals(1, primitivesCaptor.getValue().length); - VibrationEffect.Composition.PrimitiveEffect primitive = primitivesCaptor.getValue()[0]; - assertEquals(VibrationEffect.Composition.PRIMITIVE_CLICK, primitive.id); - assertEquals(0.5f, primitive.scale, /* delta= */ 1e-2); - assertEquals(10, primitive.delay); + verify(mNativeWrapperMock).compose(eq(primitives), eq(12L)); } @Test @@ -286,4 +276,8 @@ public class VibratorControllerTest { private void mockVibratorCapabilities(int capabilities) { when(mNativeWrapperMock.getCapabilities()).thenReturn((long) capabilities); } + + private PrebakedSegment createPrebaked(int effectId, int effectStrength) { + return new PrebakedSegment(effectId, /* shouldFallback= */ false, effectStrength); + } } diff --git a/services/tests/servicestests/src/com/android/server/vibrator/VibratorManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/vibrator/VibratorManagerServiceTest.java index ce6639c6b4aad..12ced388d5f77 100644 --- a/services/tests/servicestests/src/com/android/server/vibrator/VibratorManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/vibrator/VibratorManagerServiceTest.java @@ -65,6 +65,8 @@ import android.os.VibrationEffect; import android.os.Vibrator; import android.os.VibratorInfo; import android.os.test.TestLooper; +import android.os.vibrator.PrebakedSegment; +import android.os.vibrator.PrimitiveSegment; import android.platform.test.annotations.Presubmit; import android.provider.Settings; import android.view.InputDevice; @@ -364,13 +366,13 @@ public class VibratorManagerServiceTest { assertTrue(createSystemReadyService().setAlwaysOnEffect( UID, PACKAGE_NAME, 1, effect, ALARM_ATTRS)); - VibrationEffect.Prebaked expectedEffect = new VibrationEffect.Prebaked( + PrebakedSegment expected = new PrebakedSegment( VibrationEffect.EFFECT_CLICK, false, VibrationEffect.EFFECT_STRENGTH_STRONG); // Only vibrators 1 and 3 have always-on capabilities. - assertEquals(mVibratorProviders.get(1).getAlwaysOnEffect(1), expectedEffect); + assertEquals(mVibratorProviders.get(1).getAlwaysOnEffect(1), expected); assertNull(mVibratorProviders.get(2).getAlwaysOnEffect(1)); - assertEquals(mVibratorProviders.get(3).getAlwaysOnEffect(1), expectedEffect); + assertEquals(mVibratorProviders.get(3).getAlwaysOnEffect(1), expected); } @Test @@ -388,10 +390,10 @@ public class VibratorManagerServiceTest { assertTrue(createSystemReadyService().setAlwaysOnEffect( UID, PACKAGE_NAME, 1, effect, ALARM_ATTRS)); - VibrationEffect.Prebaked expectedClick = new VibrationEffect.Prebaked( + PrebakedSegment expectedClick = new PrebakedSegment( VibrationEffect.EFFECT_CLICK, false, VibrationEffect.EFFECT_STRENGTH_STRONG); - VibrationEffect.Prebaked expectedTick = new VibrationEffect.Prebaked( + PrebakedSegment expectedTick = new PrebakedSegment( VibrationEffect.EFFECT_TICK, false, VibrationEffect.EFFECT_STRENGTH_STRONG); // Enables click on vibrator 1 and tick on vibrator 2 only. @@ -487,8 +489,9 @@ public class VibratorManagerServiceTest { vibrate(service, VibrationEffect.createOneShot(40, 100), RINGTONE_ATTRS); assertTrue(waitUntil(s -> s.isVibrating(1), service, TEST_TIMEOUT_MILLIS)); - assertEquals(2, mVibratorProviders.get(1).getEffects().size()); - assertEquals(Arrays.asList(10, 100), mVibratorProviders.get(1).getAmplitudes()); + assertEquals(2, mVibratorProviders.get(1).getEffectSegments().size()); + assertEquals(Arrays.asList(10 / 255f, 100 / 255f), + mVibratorProviders.get(1).getAmplitudes()); } @Test @@ -500,19 +503,19 @@ public class VibratorManagerServiceTest { mRegisteredPowerModeListener.onLowPowerModeChanged(LOW_POWER_STATE); vibrate(service, VibrationEffect.createOneShot(1, 1), HAPTIC_FEEDBACK_ATTRS); vibrate(service, VibrationEffect.createOneShot(2, 2), RINGTONE_ATTRS); - assertTrue(waitUntil(s -> fakeVibrator.getEffects().size() == 1, + assertTrue(waitUntil(s -> fakeVibrator.getEffectSegments().size() == 1, service, TEST_TIMEOUT_MILLIS)); mRegisteredPowerModeListener.onLowPowerModeChanged(NORMAL_POWER_STATE); vibrate(service, VibrationEffect.createOneShot(3, 3), /* attributes= */ null); - assertTrue(waitUntil(s -> fakeVibrator.getEffects().size() == 2, + assertTrue(waitUntil(s -> fakeVibrator.getEffectSegments().size() == 2, service, TEST_TIMEOUT_MILLIS)); vibrate(service, VibrationEffect.createOneShot(4, 4), NOTIFICATION_ATTRS); - assertTrue(waitUntil(s -> fakeVibrator.getEffects().size() == 3, + assertTrue(waitUntil(s -> fakeVibrator.getEffectSegments().size() == 3, service, TEST_TIMEOUT_MILLIS)); - assertEquals(Arrays.asList(2, 3, 4), fakeVibrator.getAmplitudes()); + assertEquals(Arrays.asList(2 / 255f, 3 / 255f, 4 / 255f), fakeVibrator.getAmplitudes()); } @Test @@ -579,7 +582,7 @@ public class VibratorManagerServiceTest { verify(mIInputManagerMock).vibrateCombined(eq(1), eq(effect), any()); // VibrationThread will start this vibration async, so wait before checking it never played. - assertFalse(waitUntil(s -> !mVibratorProviders.get(1).getEffects().isEmpty(), + assertFalse(waitUntil(s -> !mVibratorProviders.get(1).getEffectSegments().isEmpty(), service, /* timeout= */ 50)); } @@ -640,8 +643,11 @@ public class VibratorManagerServiceTest { verify(mNativeWrapperMock).prepareSynced(eq(new int[]{1, 2})); verify(mNativeWrapperMock).triggerSynced(anyLong()); - assertEquals(Arrays.asList(composed), mVibratorProviders.get(1).getEffects()); - assertEquals(Arrays.asList(composed), mVibratorProviders.get(2).getEffects()); + + PrimitiveSegment expected = new PrimitiveSegment( + VibrationEffect.Composition.PRIMITIVE_CLICK, 1, 100); + assertEquals(Arrays.asList(expected), mVibratorProviders.get(1).getEffectSegments()); + assertEquals(Arrays.asList(expected), mVibratorProviders.get(2).getEffectSegments()); } @Test @@ -665,7 +671,7 @@ public class VibratorManagerServiceTest { .compose()) .combine(); vibrate(service, effect, ALARM_ATTRS); - assertTrue(waitUntil(s -> !fakeVibrator1.getEffects().isEmpty(), service, + assertTrue(waitUntil(s -> !fakeVibrator1.getEffectSegments().isEmpty(), service, TEST_TIMEOUT_MILLIS)); verify(mNativeWrapperMock).prepareSynced(eq(new int[]{1, 2})); @@ -689,7 +695,7 @@ public class VibratorManagerServiceTest { .addVibrator(2, VibrationEffect.createOneShot(10, 100)) .combine(); vibrate(service, effect, ALARM_ATTRS); - assertTrue(waitUntil(s -> !fakeVibrator1.getEffects().isEmpty(), service, + assertTrue(waitUntil(s -> !fakeVibrator1.getEffectSegments().isEmpty(), service, TEST_TIMEOUT_MILLIS)); verify(mNativeWrapperMock, never()).prepareSynced(any()); @@ -709,7 +715,7 @@ public class VibratorManagerServiceTest { .addVibrator(2, VibrationEffect.createOneShot(10, 100)) .combine(); vibrate(service, effect, ALARM_ATTRS); - assertTrue(waitUntil(s -> !mVibratorProviders.get(1).getEffects().isEmpty(), service, + assertTrue(waitUntil(s -> !mVibratorProviders.get(1).getEffectSegments().isEmpty(), service, TEST_TIMEOUT_MILLIS)); verify(mNativeWrapperMock).prepareSynced(eq(new int[]{1, 2})); @@ -730,7 +736,7 @@ public class VibratorManagerServiceTest { .addVibrator(2, VibrationEffect.createOneShot(10, 100)) .combine(); vibrate(service, effect, ALARM_ATTRS); - assertTrue(waitUntil(s -> !mVibratorProviders.get(1).getEffects().isEmpty(), service, + assertTrue(waitUntil(s -> !mVibratorProviders.get(1).getEffectSegments().isEmpty(), service, TEST_TIMEOUT_MILLIS)); verify(mNativeWrapperMock).prepareSynced(eq(new int[]{1, 2})); @@ -758,40 +764,38 @@ public class VibratorManagerServiceTest { vibrate(service, CombinedVibrationEffect.startSynced() .addVibrator(1, VibrationEffect.get(VibrationEffect.EFFECT_CLICK)) .combine(), ALARM_ATTRS); - assertTrue(waitUntil(s -> fakeVibrator.getEffects().size() == 1, + assertTrue(waitUntil(s -> fakeVibrator.getEffectSegments().size() == 1, service, TEST_TIMEOUT_MILLIS)); vibrate(service, CombinedVibrationEffect.startSequential() .addNext(1, VibrationEffect.createOneShot(20, 100)) .combine(), NOTIFICATION_ATTRS); - assertTrue(waitUntil(s -> fakeVibrator.getEffects().size() == 2, + assertTrue(waitUntil(s -> fakeVibrator.getEffectSegments().size() == 2, service, TEST_TIMEOUT_MILLIS)); vibrate(service, VibrationEffect.startComposition() .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1f) .addPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK, 0.5f) .compose(), HAPTIC_FEEDBACK_ATTRS); - assertTrue(waitUntil(s -> fakeVibrator.getEffects().size() == 3, + assertTrue(waitUntil(s -> fakeVibrator.getEffectSegments().size() == 4, service, TEST_TIMEOUT_MILLIS)); vibrate(service, VibrationEffect.get(VibrationEffect.EFFECT_CLICK), RINGTONE_ATTRS); - assertEquals(3, fakeVibrator.getEffects().size()); + assertEquals(4, fakeVibrator.getEffectSegments().size()); assertEquals(1, fakeVibrator.getAmplitudes().size()); // Alarm vibration is always VIBRATION_INTENSITY_HIGH. - VibrationEffect expected = new VibrationEffect.Prebaked(VibrationEffect.EFFECT_CLICK, false, - VibrationEffect.EFFECT_STRENGTH_STRONG); - assertEquals(expected, fakeVibrator.getEffects().get(0)); + PrebakedSegment expected = new PrebakedSegment( + VibrationEffect.EFFECT_CLICK, false, VibrationEffect.EFFECT_STRENGTH_STRONG); + assertEquals(expected, fakeVibrator.getEffectSegments().get(0)); // Notification vibrations will be scaled with SCALE_VERY_HIGH. - assertTrue(150 < fakeVibrator.getAmplitudes().get(0)); + assertTrue(0.6 < fakeVibrator.getAmplitudes().get(0)); // Haptic feedback vibrations will be scaled with SCALE_LOW. - VibrationEffect.Composed played = - (VibrationEffect.Composed) fakeVibrator.getEffects().get(2); - assertTrue(0.5 < played.getPrimitiveEffects().get(0).scale); - assertTrue(0.5 > played.getPrimitiveEffects().get(1).scale); + assertTrue(0.5 < ((PrimitiveSegment) fakeVibrator.getEffectSegments().get(2)).getScale()); + assertTrue(0.5 > ((PrimitiveSegment) fakeVibrator.getEffectSegments().get(3)).getScale()); // Ring vibrations have intensity OFF and are not played. } diff --git a/services/tests/uiservicestests/src/com/android/server/notification/BuzzBeepBlinkTest.java b/services/tests/uiservicestests/src/com/android/server/notification/BuzzBeepBlinkTest.java index 5462f47e3a4ca..ff881748cfeaa 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/BuzzBeepBlinkTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/BuzzBeepBlinkTest.java @@ -1688,8 +1688,8 @@ public class BuzzBeepBlinkTest extends UiServiceTestCase { @Override public boolean matches(VibrationEffect actual) { - if (actual instanceof VibrationEffect.Waveform && - ((VibrationEffect.Waveform) actual).getRepeatIndex() == mRepeatIndex) { + if (actual instanceof VibrationEffect.Composed + && ((VibrationEffect.Composed) actual).getRepeatIndex() == mRepeatIndex) { return true; } // All non-waveform effects are essentially one shots.