summaryrefslogtreecommitdiff
path: root/core/java
diff options
context:
space:
mode:
authorMichael Wright <michaelwr@google.com>2018-01-24 00:32:53 +0000
committerMichael Wright <michaelwr@google.com>2018-01-24 23:39:57 +0000
commit35a0c676eea576c8903477465e43a2ecc4dc68f6 (patch)
tree474ba60a60faebf27e777cad4636130f027a6669 /core/java
parent5e5c8d7768d823f12d1383c7f352a17045a4a374 (diff)
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
Diffstat (limited to 'core/java')
-rw-r--r--core/java/android/hardware/input/InputManager.java2
-rw-r--r--core/java/android/os/VibrationEffect.java217
-rw-r--r--core/java/android/os/Vibrator.java54
-rw-r--r--core/java/android/provider/Settings.java43
4 files changed, 270 insertions, 46 deletions
diff --git a/core/java/android/hardware/input/InputManager.java b/core/java/android/hardware/input/InputManager.java
index 1de8882e057a..fdea5a2f9351 100644
--- a/core/java/android/hardware/input/InputManager.java
+++ b/core/java/android/hardware/input/InputManager.java
@@ -1246,7 +1246,7 @@ public final class InputManager {
int repeat;
if (effect instanceof VibrationEffect.OneShot) {
VibrationEffect.OneShot oneShot = (VibrationEffect.OneShot) effect;
- pattern = new long[] { 0, oneShot.getTiming() };
+ pattern = new long[] { 0, oneShot.getDuration() };
repeat = -1;
} else if (effect instanceof VibrationEffect.Waveform) {
VibrationEffect.Waveform waveform = (VibrationEffect.Waveform) effect;
diff --git a/core/java/android/os/VibrationEffect.java b/core/java/android/os/VibrationEffect.java
index da0ed54e003e..b6f16a7b9ff8 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 abstract class VibrationEffect implements Parcelable {
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 @@ public abstract class VibrationEffect implements Parcelable {
/** @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 @@ public abstract class VibrationEffect implements Parcelable {
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 @@ public abstract class VibrationEffect implements Parcelable {
/** @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 @@ public abstract class VibrationEffect implements Parcelable {
}
@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 @@ public abstract class VibrationEffect implements Parcelable {
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 @@ public abstract class VibrationEffect implements Parcelable {
/** @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 @@ public abstract class VibrationEffect implements Parcelable {
}
@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 @@ public abstract class VibrationEffect implements Parcelable {
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 @@ public abstract class VibrationEffect implements Parcelable {
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 @@ public abstract class VibrationEffect implements Parcelable {
out.writeInt(PARCEL_TOKEN_EFFECT);
out.writeInt(mEffectId);
out.writeByte((byte) (mFallback ? 1 : 0));
+ out.writeInt(mEffectStrength);
}
public static final Parcelable.Creator<Prebaked> CREATOR =
diff --git a/core/java/android/os/Vibrator.java b/core/java/android/os/Vibrator.java
index 8078fb87eb27..f1f6f414eba8 100644
--- a/core/java/android/os/Vibrator.java
+++ b/core/java/android/os/Vibrator.java
@@ -16,6 +16,7 @@
package android.os;
+import android.annotation.IntDef;
import android.annotation.RequiresPermission;
import android.annotation.SystemService;
import android.app.ActivityThread;
@@ -23,6 +24,9 @@ import android.content.Context;
import android.media.AudioAttributes;
import android.util.Log;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
/**
* Class that operates the vibrator on the device.
* <p>
@@ -33,6 +37,40 @@ import android.util.Log;
public abstract class Vibrator {
private static final String TAG = "Vibrator";
+ /**
+ * Vibration intensity: no vibrations.
+ * @hide
+ */
+ public static final int VIBRATION_INTENSITY_OFF = 0;
+
+ /**
+ * Vibration intensity: low.
+ * @hide
+ */
+ public static final int VIBRATION_INTENSITY_LOW = 1;
+
+ /**
+ * Vibration intensity: medium.
+ * @hide
+ */
+ public static final int VIBRATION_INTENSITY_MEDIUM = 2;
+
+ /**
+ * Vibration intensity: high.
+ * @hide
+ */
+ public static final int VIBRATION_INTENSITY_HIGH = 3;
+
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(prefix = { "VIBRATION_INTENSITY_" }, value = {
+ VIBRATION_INTENSITY_OFF,
+ VIBRATION_INTENSITY_LOW,
+ VIBRATION_INTENSITY_MEDIUM,
+ VIBRATION_INTENSITY_HIGH
+ })
+ public @interface VibrationIntensity{}
+
private final String mPackageName;
/**
@@ -50,6 +88,22 @@ public abstract class Vibrator {
}
/**
+ * Get the default vibration intensity for haptic feedback.
+ * @hide
+ */
+ public int getDefaultHapticFeedbackIntensity() {
+ return VIBRATION_INTENSITY_MEDIUM;
+ }
+
+ /**
+ * Get the default vibration intensity for notifications and ringtones.
+ * @hide
+ */
+ public int getDefaultNotificationVibrationIntensity() {
+ return VIBRATION_INTENSITY_HIGH;
+ }
+
+ /**
* Check whether the hardware has a vibrator.
*
* @return True if the hardware has a vibrator, else false.
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index daf6bd571932..59f75e3bd250 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -3175,6 +3175,43 @@ public final class Settings {
private static final Validator VIBRATE_INPUT_DEVICES_VALIDATOR = BOOLEAN_VALIDATOR;
/**
+ * The intensity of notification vibrations, if configurable.
+ *
+ * Not all devices are capable of changing their vibration intensity; on these devices
+ * there will likely be no difference between the various vibration intensities except for
+ * intensity 0 (off) and the rest.
+ *
+ * <b>Values:</b><br/>
+ * 0 - Vibration is disabled<br/>
+ * 1 - Weak vibrations<br/>
+ * 2 - Medium vibrations<br/>
+ * 3 - Strong vibrations
+ * @hide
+ */
+ public static final String NOTIFICATION_VIBRATION_INTENSITY =
+ "notification_vibration_intensity";
+
+ /**
+ * The intensity of haptic feedback vibrations, if configurable.
+ *
+ * Not all devices are capable of changing their feedback intensity; on these devices
+ * there will likely be no difference between the various vibration intensities except for
+ * intensity 0 (off) and the rest.
+ *
+ * <b>Values:</b><br/>
+ * 0 - Vibration is disabled<br/>
+ * 1 - Weak vibrations<br/>
+ * 2 - Medium vibrations<br/>
+ * 3 - Strong vibrations
+ * @hide
+ */
+ public static final String HAPTIC_FEEDBACK_INTENSITY =
+ "haptic_feedback_intensity";
+
+ private static final Validator VIBRATION_INTENSITY_VALIDATOR =
+ new SettingsValidators.InclusiveIntegerRangeValidator(0, 3);
+
+ /**
* Ringer volume. This is used internally, changing this value will not
* change the volume. See AudioManager.
*
@@ -3995,7 +4032,9 @@ public final class Settings {
LOCK_TO_APP_ENABLED,
NOTIFICATION_SOUND,
ACCELEROMETER_ROTATION,
- SHOW_BATTERY_PERCENT
+ SHOW_BATTERY_PERCENT,
+ NOTIFICATION_VIBRATION_INTENSITY,
+ HAPTIC_FEEDBACK_INTENSITY,
};
/**
@@ -4136,6 +4175,8 @@ public final class Settings {
VALIDATORS.put(MODE_RINGER_STREAMS_AFFECTED, MODE_RINGER_STREAMS_AFFECTED_VALIDATOR);
VALIDATORS.put(MUTE_STREAMS_AFFECTED, MUTE_STREAMS_AFFECTED_VALIDATOR);
VALIDATORS.put(VIBRATE_ON, VIBRATE_ON_VALIDATOR);
+ VALIDATORS.put(NOTIFICATION_VIBRATION_INTENSITY, VIBRATION_INTENSITY_VALIDATOR);
+ VALIDATORS.put(HAPTIC_FEEDBACK_INTENSITY, VIBRATION_INTENSITY_VALIDATOR);
VALIDATORS.put(RINGTONE, RINGTONE_VALIDATOR);
VALIDATORS.put(NOTIFICATION_SOUND, NOTIFICATION_SOUND_VALIDATOR);
VALIDATORS.put(ALARM_ALERT, ALARM_ALERT_VALIDATOR);