diff options
| author | Michael Wright <michaelwr@google.com> | 2018-01-24 00:32:53 +0000 |
|---|---|---|
| committer | Michael Wright <michaelwr@google.com> | 2018-01-24 23:39:57 +0000 |
| commit | 35a0c676eea576c8903477465e43a2ecc4dc68f6 (patch) | |
| tree | 474ba60a60faebf27e777cad4636130f027a6669 /core/java | |
| parent | 5e5c8d7768d823f12d1383c7f352a17045a4a374 (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.java | 2 | ||||
| -rw-r--r-- | core/java/android/os/VibrationEffect.java | 217 | ||||
| -rw-r--r-- | core/java/android/os/Vibrator.java | 54 | ||||
| -rw-r--r-- | core/java/android/provider/Settings.java | 43 |
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); |
