Merge "Introduce VibrationEffectSegment" into sc-dev

This commit is contained in:
TreeHugger Robot
2021-03-23 08:52:21 +00:00
committed by Android (Google) Code Review
32 changed files with 1914 additions and 1485 deletions

View File

@@ -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<android.os.VibrationEffect.OneShot> 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<android.os.VibrationEffect.Prebaked> 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<android.os.VibrationEffect.Waveform> CREATOR;
method @NonNull public java.util.List<android.os.vibrator.VibrationEffectSegment> 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<android.os.VibrationEffect.Composed> 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<android.os.vibrator.PrebakedSegment> 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<android.os.vibrator.PrimitiveSegment> 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<android.os.vibrator.StepSegment> CREATOR;
}
public abstract class VibrationEffectSegment implements android.os.Parcelable {
method @NonNull public abstract <T extends android.os.vibrator.VibrationEffectSegment> T applyEffectStrength(int);
method public abstract long getDuration();
method public abstract boolean hasNonZeroAmplitude();
method @NonNull public abstract <T extends android.os.vibrator.VibrationEffectSegment> T resolve(int);
method @NonNull public abstract <T extends android.os.vibrator.VibrationEffectSegment> T scale(float);
method public abstract void validate();
field @NonNull public static final android.os.Parcelable.Creator<android.os.vibrator.VibrationEffectSegment> CREATOR;
}
}
package android.permission {
public final class PermissionControllerManager {

View File

@@ -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:

View File

@@ -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.");

View File

@@ -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]);

View File

@@ -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()) {

View File

@@ -17,4 +17,3 @@
package android.os;
parcelable VibrationEffect;
parcelable VibrationEffect.Composition.PrimitiveEffect;

View File

@@ -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<StepSegment> 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 extends VibrationEffect> 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 extends VibrationEffect> 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<VibrationEffectSegment> 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<? extends VibrationEffectSegment> 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<OneShot> CREATOR =
new Parcelable.Creator<OneShot>() {
@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<VibrationEffectSegment> 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<Waveform> CREATOR =
new Parcelable.Creator<Waveform>() {
@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<Prebaked> CREATOR =
new Parcelable.Creator<Prebaked>() {
@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<Composition.PrimitiveEffect> mPrimitiveEffects;
/**
* @hide
*/
@SuppressWarnings("unchecked")
public Composed(@NonNull Parcel in) {
this(in.readArrayList(Composed.class.getClassLoader()));
}
/**
* @hide
*/
public Composed(List<Composition.PrimitiveEffect> effects) {
mPrimitiveEffects = new ArrayList<>(Objects.requireNonNull(effects));
}
/**
* @hide
*/
@NonNull
public List<Composition.PrimitiveEffect> getPrimitiveEffects() {
return mPrimitiveEffects;
}
@Override
public long getDuration() {
return -1;
public Composed resolve(int defaultAmplitude) {
int segmentCount = mSegments.size();
ArrayList<VibrationEffectSegment> 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<VibrationEffectSegment> 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<Composition.PrimitiveEffect> 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<VibrationEffectSegment> 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<Composed> CREATOR =
new Parcelable.Creator<Composed>() {
@NonNull
public static final Creator<Composed> CREATOR =
new Creator<Composed>() {
@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<PrimitiveEffect> mEffects = new ArrayList<>();
private final ArrayList<VibrationEffectSegment> 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<PrimitiveEffect> CREATOR =
new Parcelable.Creator<PrimitiveEffect>() {
@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<VibrationEffect> CREATOR =
@NonNull
public static final Parcelable.Creator<VibrationEffect> CREATOR =
new Parcelable.Creator<VibrationEffect>() {
@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) {

View File

@@ -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;

View File

@@ -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);
}

View File

@@ -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<PrebakedSegment> CREATOR =
new Parcelable.Creator<PrebakedSegment>() {
@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];
}
};
}

View File

@@ -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<PrimitiveSegment> CREATOR =
new Parcelable.Creator<PrimitiveSegment>() {
@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];
}
};
}

View File

@@ -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<StepSegment> CREATOR =
new Parcelable.Creator<StepSegment>() {
@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];
}
};
}

View File

@@ -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}.
*
* <p>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:
*
* <ol>
* <li>A predefined vibration effect;
* <li>A composable effect primitive;
* <li>Fixed amplitude value to be held for a specified duration;
* </ol>
*
* @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.
*
* <p>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}.
*
* <p>This might fail with {@link IllegalArgumentException} if value is non-positive or larger
* than {@link VibrationEffect#MAX_AMPLITUDE}.
*/
@NonNull
public abstract <T extends VibrationEffectSegment> 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 extends VibrationEffectSegment> 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 extends VibrationEffectSegment> T applyEffectStrength(int effectStrength);
@NonNull
public static final Creator<VibrationEffectSegment> CREATOR =
new Creator<VibrationEffectSegment>() {
@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];
}
};
}

View File

@@ -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 {

View File

@@ -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() {

View File

@@ -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() {

View File

@@ -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));
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}
}
}

View File

@@ -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<VibrationEffect> 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<VibrationEffect, VibrationEffect> 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<VibrationEffect, VibrationEffect> 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<VibrationEffect> 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<CombinedVibrationEffect> 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);
}
}

View File

@@ -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 extends CombinedVibrationEffect> 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<VibrationEffect> 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<CombinedVibrationEffect> 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 extends VibrationEffect> 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}. */

View File

@@ -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<VibrationEffectSegment> 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<Step> 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<Step> 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<VibrationEffect> 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;
}
}

View File

@@ -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}.
*
* <p>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. */

View File

@@ -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<VibrationEffect.Prebaked> effects = fixupAlwaysOnEffectsLocked(effect);
SparseArray<PrebakedSegment> 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<VibrationEffect> 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<CombinedVibrationEffect> 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<VibrationEffect.Prebaked> fixupAlwaysOnEffectsLocked(
private SparseArray<PrebakedSegment> 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<VibrationEffect.Prebaked> result = new SparseArray<>();
SparseArray<PrebakedSegment> 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<VibrationEffect.Prebaked> effects;
public final SparseArray<PrebakedSegment> effects;
AlwaysOnVibration(int alwaysOnId, int uid, String opPkg, VibrationAttributes attrs,
SparseArray<VibrationEffect.Prebaked> effects) {
SparseArray<PrebakedSegment> effects) {
this.alwaysOnId = alwaysOnId;
this.uid = uid;
this.opPkg = opPkg;

View File

@@ -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<VibratorControllerWrapper*>(ptr);
if (wrapper == nullptr) {
ALOGE("vibratorSetAmplitude failed because native wrapper was not initialized");
return;
}
wrapper->hal()->setAmplitude(static_cast<int32_t>(amplitude));
wrapper->hal()->setAmplitude(static_cast<float>(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",

View File

@@ -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<Long, VibrationEffect.Prebaked> mEnabledAlwaysOnEffects = new HashMap<>();
private final List<VibrationEffect> mEffects = new ArrayList<>();
private final List<Integer> mAmplitudes = new ArrayList<>();
private final Map<Long, PrebakedSegment> mEnabledAlwaysOnEffects = new HashMap<>();
private final List<VibrationEffectSegment> mEffectSegments = new ArrayList<>();
private final List<Float> 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<Integer> getAmplitudes() {
public List<Float> getAmplitudes() {
return new ArrayList<>(mAmplitudes);
}
/** Return list of {@link VibrationEffect} played by this controller, in order. */
public List<VibrationEffect> getEffects() {
return new ArrayList<>(mEffects);
/** Return list of {@link VibrationEffectSegment} played by this controller, in order. */
public List<VibrationEffectSegment> 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);
}
}

View File

@@ -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 extends VibrationEffectSegment> T getFirstSegment(VibrationEffect.Composed effect) {
return (T) effect.getSegments().get(0);
}
private void setUserSetting(String settingName, int value) {

View File

@@ -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<Integer> playedAmplitudes = fakeVibrator.getAmplitudes();
assertFalse(fakeVibrator.getEffects().isEmpty());
List<Float> 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<Float> expectedAmplitudes(int... amplitudes) {
return Arrays.stream(amplitudes)
.mapToObj(amplitude -> amplitude / 255f)
.collect(Collectors.toList());
}
}

View File

@@ -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<VibrationEffect.Composition.PrimitiveEffect[]> 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);
}
}

View File

@@ -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.
}

View File

@@ -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.