Add setting to control vibration intensity.
This patch adds two distinct vibration control settings: one for
notifications and ringtones, and one for haptic feedback. Since we don't
always have the exact intent of a given vibration, VibratorService will
do its best to classify each VibrationEffect into one of these two
categories and then scale the vibration accordingly based on the
intensity setting.
Bug: 64185329
Test: cts-tradefed run commandAndExit cts-dev -m CtsOsTestCases -t android.os.cts.VibratorTest
cts-tradefed run commandAndExit cts-dev -m CtsOsTestCases -t android.os.cts.VibrationEffectTest
Change-Id: If16237f4782281aaab33e4a0f55c29f1a30ac493
diff --git a/core/java/android/os/VibrationEffect.java b/core/java/android/os/VibrationEffect.java
index da0ed54..b6f16a7 100644
--- a/core/java/android/os/VibrationEffect.java
+++ b/core/java/android/os/VibrationEffect.java
@@ -16,7 +16,9 @@
package android.os;
+import android.hardware.vibrator.V1_0.Constants.EffectStrength;
import android.hardware.vibrator.V1_1.Constants.Effect_1_1;
+import android.util.MathUtils;
import java.util.Arrays;
@@ -36,6 +38,12 @@
public static final int DEFAULT_AMPLITUDE = -1;
/**
+ * The maximum amplitude value
+ * @hide
+ */
+ public static final int MAX_AMPLITUDE = 255;
+
+ /**
* A click effect.
*
* @see #get(int)
@@ -198,38 +206,75 @@
/** @hide */
public abstract void validate();
+ /**
+ * Gets the estimated duration of the vibration in milliseconds.
+ *
+ * For effects without a defined end (e.g. a Waveform with a non-negative repeat index), this
+ * returns Long.MAX_VALUE. For effects with an unknown duration (e.g. Prebaked effects where
+ * the length is device and potentially run-time dependent), this returns -1.
+ *
+ * @hide
+ */
+ public abstract long getDuration();
+
+ /**
+ * Scale the amplitude with the given constraints.
+ *
+ * This assumes that the previous value was in the range [0, MAX_AMPLITUDE]
+ * @hide
+ */
+ protected static int scale(int amplitude, float gamma, int maxAmplitude) {
+ float val = MathUtils.pow(amplitude / (float) MAX_AMPLITUDE, gamma);
+ return (int) (val * maxAmplitude);
+ }
+
/** @hide */
public static class OneShot extends VibrationEffect implements Parcelable {
- private long mTiming;
- private int mAmplitude;
+ private final long mDuration;
+ private final int mAmplitude;
public OneShot(Parcel in) {
- this(in.readLong(), in.readInt());
+ mDuration = in.readLong();
+ mAmplitude = in.readInt();
}
public OneShot(long milliseconds, int amplitude) {
- mTiming = milliseconds;
+ mDuration = milliseconds;
mAmplitude = amplitude;
}
- public long getTiming() {
- return mTiming;
+ @Override
+ public long getDuration() {
+ return mDuration;
}
public int getAmplitude() {
return mAmplitude;
}
+ /**
+ * Scale the amplitude of this effect.
+ *
+ * @param gamma the gamma adjustment to apply
+ * @param maxAmplitude the new maximum amplitude of the effect
+ *
+ * @return A {@link OneShot} effect with the same timing but scaled amplitude.
+ */
+ public VibrationEffect scale(float gamma, int maxAmplitude) {
+ int newAmplitude = scale(mAmplitude, gamma, maxAmplitude);
+ return new OneShot(mDuration, newAmplitude);
+ }
+
@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 + ")");
+ "amplitude must either be DEFAULT_AMPLITUDE, "
+ + "or between 1 and 255 inclusive (amplitude=" + mAmplitude + ")");
}
- if (mTiming <= 0) {
+ if (mDuration <= 0) {
throw new IllegalArgumentException(
- "timing must be positive (timing=" + mTiming + ")");
+ "duration must be positive (duration=" + mDuration + ")");
}
}
@@ -239,26 +284,26 @@
return false;
}
VibrationEffect.OneShot other = (VibrationEffect.OneShot) o;
- return other.mTiming == mTiming && other.mAmplitude == mAmplitude;
+ return other.mDuration == mDuration && other.mAmplitude == mAmplitude;
}
@Override
public int hashCode() {
int result = 17;
- result = 37 * (int) mTiming;
- result = 37 * mAmplitude;
+ result += 37 * (int) mDuration;
+ result += 37 * mAmplitude;
return result;
}
@Override
public String toString() {
- return "OneShot{mTiming=" + mTiming +", mAmplitude=" + mAmplitude + "}";
+ return "OneShot{mDuration=" + mDuration + ", mAmplitude=" + mAmplitude + "}";
}
@Override
public void writeToParcel(Parcel out, int flags) {
out.writeInt(PARCEL_TOKEN_ONE_SHOT);
- out.writeLong(mTiming);
+ out.writeLong(mDuration);
out.writeInt(mAmplitude);
}
@@ -279,9 +324,9 @@
/** @hide */
public static class Waveform extends VibrationEffect implements Parcelable {
- private long[] mTimings;
- private int[] mAmplitudes;
- private int mRepeat;
+ private final long[] mTimings;
+ private final int[] mAmplitudes;
+ private final int mRepeat;
public Waveform(Parcel in) {
this(in.createLongArray(), in.createIntArray(), in.readInt());
@@ -308,34 +353,68 @@
}
@Override
+ public long getDuration() {
+ if (mRepeat >= 0) {
+ return Long.MAX_VALUE;
+ }
+ long duration = 0;
+ for (long d : mTimings) {
+ duration += d;
+ }
+ return duration;
+ }
+
+ /**
+ * Scale the Waveform with the given gamma and new max amplitude.
+ *
+ * @param gamma the gamma adjustment to apply
+ * @param maxAmplitude the new maximum amplitude of the effect
+ *
+ * @return A {@link Waveform} effect with the same timings and repeat index
+ * but scaled amplitude.
+ */
+ public VibrationEffect scale(float gamma, int maxAmplitude) {
+ if (gamma == 1.0f && maxAmplitude == MAX_AMPLITUDE) {
+ // Just return a copy of the original if there's no scaling to be done.
+ return new Waveform(mTimings, mAmplitudes, mRepeat);
+ }
+
+ int[] scaledAmplitudes = Arrays.copyOf(mAmplitudes, mAmplitudes.length);
+ for (int i = 0; i < scaledAmplitudes.length; i++) {
+ scaledAmplitudes[i] = scale(scaledAmplitudes[i], gamma, maxAmplitude);
+ }
+ return new Waveform(mTimings, scaledAmplitudes, mRepeat);
+ }
+
+ @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 + ")");
+ "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) + ")");
+ 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) + ")");
+ 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) + ")");
+ "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 +")");
+ "repeat index must be within the bounds of the timings array"
+ + " (timings.length=" + mTimings.length + ", index=" + mRepeat + ")");
}
}
@@ -345,26 +424,26 @@
return false;
}
VibrationEffect.Waveform other = (VibrationEffect.Waveform) o;
- return Arrays.equals(mTimings, other.mTimings) &&
- Arrays.equals(mAmplitudes, other.mAmplitudes) &&
- mRepeat == other.mRepeat;
+ 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;
+ 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 +
- "}";
+ return "Waveform{mTimings=" + Arrays.toString(mTimings)
+ + ", mAmplitudes=" + Arrays.toString(mAmplitudes)
+ + ", mRepeat=" + mRepeat
+ + "}";
}
@Override
@@ -402,16 +481,20 @@
/** @hide */
public static class Prebaked extends VibrationEffect implements Parcelable {
- private int mEffectId;
- private boolean mFallback;
+ private final int mEffectId;
+ private final boolean mFallback;
+
+ private int mEffectStrength;
public Prebaked(Parcel in) {
this(in.readInt(), in.readByte() != 0);
+ mEffectStrength = in.readInt();
}
public Prebaked(int effectId, boolean fallback) {
mEffectId = effectId;
mFallback = fallback;
+ mEffectStrength = EffectStrength.MEDIUM;
}
public int getId() {
@@ -427,6 +510,39 @@
}
@Override
+ public long getDuration() {
+ return -1;
+ }
+
+ /**
+ * Set the effect strength of the prebaked effect.
+ */
+ public void setEffectStrength(int strength) {
+ if (!isValidEffectStrength(strength)) {
+ throw new IllegalArgumentException("Invalid effect strength: " + strength);
+ }
+ mEffectStrength = strength;
+ }
+
+ /**
+ * Set the effect strength.
+ */
+ public int getEffectStrength() {
+ return mEffectStrength;
+ }
+
+ private static boolean isValidEffectStrength(int strength) {
+ switch (strength) {
+ case EffectStrength.LIGHT:
+ case EffectStrength.MEDIUM:
+ case EffectStrength.STRONG:
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ @Override
public void validate() {
switch (mEffectId) {
case EFFECT_CLICK:
@@ -437,6 +553,10 @@
throw new IllegalArgumentException(
"Unknown prebaked effect type (value=" + mEffectId + ")");
}
+ if (!isValidEffectStrength(mEffectStrength)) {
+ throw new IllegalArgumentException(
+ "Unknown prebaked effect strength (value=" + mEffectStrength + ")");
+ }
}
@Override
@@ -445,17 +565,25 @@
return false;
}
VibrationEffect.Prebaked other = (VibrationEffect.Prebaked) o;
- return mEffectId == other.mEffectId && mFallback == other.mFallback;
+ return mEffectId == other.mEffectId
+ && mFallback == other.mFallback
+ && mEffectStrength == other.mEffectStrength;
}
@Override
public int hashCode() {
- return mEffectId;
+ int result = 17;
+ result += 37 * mEffectId;
+ result += 37 * mEffectStrength;
+ return result;
}
@Override
public String toString() {
- return "Prebaked{mEffectId=" + mEffectId + ", mFallback=" + mFallback + "}";
+ return "Prebaked{mEffectId=" + mEffectId
+ + ", mEffectStrength=" + mEffectStrength
+ + ", mFallback=" + mFallback
+ + "}";
}
@@ -464,6 +592,7 @@
out.writeInt(PARCEL_TOKEN_EFFECT);
out.writeInt(mEffectId);
out.writeByte((byte) (mFallback ? 1 : 0));
+ out.writeInt(mEffectStrength);
}
public static final Parcelable.Creator<Prebaked> CREATOR =