summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Android.mk1
-rw-r--r--api/current.txt20
-rw-r--r--api/system-current.txt20
-rw-r--r--api/test-current.txt20
-rw-r--r--core/java/android/hardware/input/InputManager.java29
-rw-r--r--core/java/android/os/IVibratorService.aidl6
-rw-r--r--core/java/android/os/NullVibrator.java16
-rw-r--r--core/java/android/os/SystemVibrator.java42
-rw-r--r--core/java/android/os/VibrationEffect.aidl22
-rw-r--r--core/java/android/os/VibrationEffect.java444
-rw-r--r--core/java/android/os/Vibrator.java52
-rw-r--r--core/res/res/values/config.xml3
-rw-r--r--core/res/res/values/symbols.xml1
-rw-r--r--services/core/Android.mk1
-rw-r--r--services/core/java/com/android/server/VibratorService.java796
-rw-r--r--services/core/java/com/android/server/notification/NotificationManagerService.java10
-rw-r--r--services/core/java/com/android/server/policy/PhoneWindowManager.java47
-rw-r--r--services/core/jni/com_android_server_VibratorService.cpp63
-rw-r--r--services/tests/notification/src/com/android/server/notification/BuzzBeepBlinkTest.java55
-rw-r--r--tests/permission/src/com/android/framework/permission/tests/VibratorServicePermissionTest.java22
20 files changed, 1188 insertions, 482 deletions
diff --git a/Android.mk b/Android.mk
index 03b2533e8bc0..b46aeb891008 100644
--- a/Android.mk
+++ b/Android.mk
@@ -568,6 +568,7 @@ LOCAL_STATIC_JAVA_LIBRARIES := \
android.hardware.thermal@1.0-java-constants \
android.hardware.health@1.0-java-constants \
android.hardware.usb@1.0-java-constants \
+ android.hardware.vibrator@1.0-java-constants \
LOCAL_PROTOC_OPTIMIZE_TYPE := stream
LOCAL_PROTOC_FLAGS := \
diff --git a/api/current.txt b/api/current.txt
index aab29be21d0f..b18457fe5d1d 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -31654,13 +31654,25 @@ package android.os {
field public static final int USER_CREATION_FAILED_NO_MORE_USERS = 2; // 0x2
}
+ public abstract class VibrationEffect implements android.os.Parcelable {
+ method public static android.os.VibrationEffect createOneShot(long, int);
+ method public static android.os.VibrationEffect createWaveform(long[], int);
+ method public static android.os.VibrationEffect createWaveform(long[], int[], int);
+ method public int describeContents();
+ field public static final android.os.Parcelable.Creator<android.os.VibrationEffect> CREATOR;
+ field public static final int DEFAULT_AMPLITUDE = -1; // 0xffffffff
+ }
+
public abstract class Vibrator {
method public abstract void cancel();
+ method public abstract boolean hasAmplitudeControl();
method public abstract boolean hasVibrator();
- method public void vibrate(long);
- method public void vibrate(long, android.media.AudioAttributes);
- method public void vibrate(long[], int);
- method public void vibrate(long[], int, android.media.AudioAttributes);
+ method public deprecated void vibrate(long);
+ method public deprecated void vibrate(long, android.media.AudioAttributes);
+ method public deprecated void vibrate(long[], int);
+ method public deprecated void vibrate(long[], int, android.media.AudioAttributes);
+ method public void vibrate(android.os.VibrationEffect);
+ method public void vibrate(android.os.VibrationEffect, android.media.AudioAttributes);
}
public class WorkSource implements android.os.Parcelable {
diff --git a/api/system-current.txt b/api/system-current.txt
index 5e6717cbece4..87b2e4dc5a86 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -34494,13 +34494,25 @@ package android.os {
public static abstract class UserManager.UserRestrictionSource implements java.lang.annotation.Annotation {
}
+ public abstract class VibrationEffect implements android.os.Parcelable {
+ method public static android.os.VibrationEffect createOneShot(long, int);
+ method public static android.os.VibrationEffect createWaveform(long[], int);
+ method public static android.os.VibrationEffect createWaveform(long[], int[], int);
+ method public int describeContents();
+ field public static final android.os.Parcelable.Creator<android.os.VibrationEffect> CREATOR;
+ field public static final int DEFAULT_AMPLITUDE = -1; // 0xffffffff
+ }
+
public abstract class Vibrator {
method public abstract void cancel();
+ method public abstract boolean hasAmplitudeControl();
method public abstract boolean hasVibrator();
- method public void vibrate(long);
- method public void vibrate(long, android.media.AudioAttributes);
- method public void vibrate(long[], int);
- method public void vibrate(long[], int, android.media.AudioAttributes);
+ method public deprecated void vibrate(long);
+ method public deprecated void vibrate(long, android.media.AudioAttributes);
+ method public deprecated void vibrate(long[], int);
+ method public deprecated void vibrate(long[], int, android.media.AudioAttributes);
+ method public void vibrate(android.os.VibrationEffect);
+ method public void vibrate(android.os.VibrationEffect, android.media.AudioAttributes);
}
public class WorkSource implements android.os.Parcelable {
diff --git a/api/test-current.txt b/api/test-current.txt
index f91bbb9a045b..9bac49f9232a 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -31779,13 +31779,25 @@ package android.os {
field public static final int USER_CREATION_FAILED_NO_MORE_USERS = 2; // 0x2
}
+ public abstract class VibrationEffect implements android.os.Parcelable {
+ method public static android.os.VibrationEffect createOneShot(long, int);
+ method public static android.os.VibrationEffect createWaveform(long[], int);
+ method public static android.os.VibrationEffect createWaveform(long[], int[], int);
+ method public int describeContents();
+ field public static final android.os.Parcelable.Creator<android.os.VibrationEffect> CREATOR;
+ field public static final int DEFAULT_AMPLITUDE = -1; // 0xffffffff
+ }
+
public abstract class Vibrator {
method public abstract void cancel();
+ method public abstract boolean hasAmplitudeControl();
method public abstract boolean hasVibrator();
- method public void vibrate(long);
- method public void vibrate(long, android.media.AudioAttributes);
- method public void vibrate(long[], int);
- method public void vibrate(long[], int, android.media.AudioAttributes);
+ method public deprecated void vibrate(long);
+ method public deprecated void vibrate(long, android.media.AudioAttributes);
+ method public deprecated void vibrate(long[], int);
+ method public deprecated void vibrate(long[], int, android.media.AudioAttributes);
+ method public void vibrate(android.os.VibrationEffect);
+ method public void vibrate(android.os.VibrationEffect, android.media.AudioAttributes);
}
public class WorkSource implements android.os.Parcelable {
diff --git a/core/java/android/hardware/input/InputManager.java b/core/java/android/hardware/input/InputManager.java
index 6e202b00274f..631b77d4132c 100644
--- a/core/java/android/hardware/input/InputManager.java
+++ b/core/java/android/hardware/input/InputManager.java
@@ -33,6 +33,7 @@ import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.SystemClock;
import android.os.Vibrator;
+import android.os.VibrationEffect;
import android.os.ServiceManager.ServiceNotFoundException;
import android.provider.Settings;
import android.provider.Settings.SettingNotFoundException;
@@ -1154,23 +1155,33 @@ public final class InputManager {
return true;
}
- /**
- * @hide
- */
@Override
- public void vibrate(int uid, String opPkg, long milliseconds, AudioAttributes attributes) {
- vibrate(new long[] { 0, milliseconds}, -1);
+ public boolean hasAmplitudeControl() {
+ return false;
}
/**
* @hide
*/
@Override
- public void vibrate(int uid, String opPkg, long[] pattern, int repeat,
- AudioAttributes attributes) {
- if (repeat >= pattern.length) {
- throw new ArrayIndexOutOfBoundsException();
+ public void vibrate(int uid, String opPkg,
+ VibrationEffect effect, AudioAttributes attributes) {
+ long[] pattern;
+ int repeat;
+ if (effect instanceof VibrationEffect.OneShot) {
+ VibrationEffect.OneShot oneShot = (VibrationEffect.OneShot) effect;
+ pattern = new long[] { 0, oneShot.getTiming() };
+ repeat = -1;
+ } else if (effect instanceof VibrationEffect.Waveform) {
+ VibrationEffect.Waveform waveform = (VibrationEffect.Waveform) effect;
+ pattern = waveform.getTimings();
+ repeat = waveform.getRepeatIndex();
+ } else {
+ // TODO: Add support for prebaked effects
+ Log.w(TAG, "Pre-baked effects aren't supported on input devices");
+ return;
}
+
try {
mIm.vibrate(mDeviceId, pattern, repeat, mToken);
} catch (RemoteException ex) {
diff --git a/core/java/android/os/IVibratorService.aidl b/core/java/android/os/IVibratorService.aidl
index 6f2857d46625..e59c3ae16ef7 100644
--- a/core/java/android/os/IVibratorService.aidl
+++ b/core/java/android/os/IVibratorService.aidl
@@ -16,12 +16,14 @@
package android.os;
+import android.os.VibrationEffect;
+
/** {@hide} */
interface IVibratorService
{
boolean hasVibrator();
- void vibrate(int uid, String opPkg, long milliseconds, int usageHint, IBinder token);
- void vibratePattern(int uid, String opPkg, in long[] pattern, int repeat, int usageHint, IBinder token);
+ boolean hasAmplitudeControl();
+ void vibrate(int uid, String opPkg, in VibrationEffect effect, int usageHint, IBinder token);
void cancelVibrate(IBinder token);
}
diff --git a/core/java/android/os/NullVibrator.java b/core/java/android/os/NullVibrator.java
index 19b452f323ce..b8bdc89a2f72 100644
--- a/core/java/android/os/NullVibrator.java
+++ b/core/java/android/os/NullVibrator.java
@@ -38,22 +38,14 @@ public class NullVibrator extends Vibrator {
return false;
}
- /**
- * @hide
- */
@Override
- public void vibrate(int uid, String opPkg, long milliseconds, AudioAttributes attributes) {
+ public boolean hasAmplitudeControl() {
+ return false;
}
- /**
- * @hide
- */
@Override
- public void vibrate(int uid, String opPkg, long[] pattern, int repeat,
- AudioAttributes attributes) {
- if (repeat >= pattern.length) {
- throw new ArrayIndexOutOfBoundsException();
- }
+ public void vibrate(int uid, String opPkg,
+ VibrationEffect effect, AudioAttributes attributes) {
}
@Override
diff --git a/core/java/android/os/SystemVibrator.java b/core/java/android/os/SystemVibrator.java
index c4888111d0ef..f776c17e56be 100644
--- a/core/java/android/os/SystemVibrator.java
+++ b/core/java/android/os/SystemVibrator.java
@@ -32,14 +32,12 @@ public class SystemVibrator extends Vibrator {
private final Binder mToken = new Binder();
public SystemVibrator() {
- mService = IVibratorService.Stub.asInterface(
- ServiceManager.getService("vibrator"));
+ mService = IVibratorService.Stub.asInterface(ServiceManager.getService("vibrator"));
}
public SystemVibrator(Context context) {
super(context);
- mService = IVibratorService.Stub.asInterface(
- ServiceManager.getService("vibrator"));
+ mService = IVibratorService.Stub.asInterface(ServiceManager.getService("vibrator"));
}
@Override
@@ -55,44 +53,30 @@ public class SystemVibrator extends Vibrator {
return false;
}
- /**
- * @hide
- */
@Override
- public void vibrate(int uid, String opPkg, long milliseconds, AudioAttributes attributes) {
+ public boolean hasAmplitudeControl() {
if (mService == null) {
- Log.w(TAG, "Failed to vibrate; no vibrator service.");
- return;
+ Log.w(TAG, "Failed to check amplitude control; no vibrator service.");
+ return false;
}
try {
- mService.vibrate(uid, opPkg, milliseconds, usageForAttributes(attributes), mToken);
+ return mService.hasAmplitudeControl();
} catch (RemoteException e) {
- Log.w(TAG, "Failed to vibrate.", e);
}
+ return false;
}
- /**
- * @hide
- */
@Override
- public void vibrate(int uid, String opPkg, long[] pattern, int repeat,
- AudioAttributes attributes) {
+ public void vibrate(int uid, String opPkg,
+ VibrationEffect effect, AudioAttributes attributes) {
if (mService == null) {
Log.w(TAG, "Failed to vibrate; no vibrator service.");
return;
}
- // catch this here because the server will do nothing. pattern may
- // not be null, let that be checked, because the server will drop it
- // anyway
- if (repeat < pattern.length) {
- try {
- mService.vibratePattern(uid, opPkg, pattern, repeat, usageForAttributes(attributes),
- mToken);
- } catch (RemoteException e) {
- Log.w(TAG, "Failed to vibrate.", e);
- }
- } else {
- throw new ArrayIndexOutOfBoundsException();
+ try {
+ mService.vibrate(uid, opPkg, effect, usageForAttributes(attributes), mToken);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed to vibrate.", e);
}
}
diff --git a/core/java/android/os/VibrationEffect.aidl b/core/java/android/os/VibrationEffect.aidl
new file mode 100644
index 000000000000..dcc79d798c3d
--- /dev/null
+++ b/core/java/android/os/VibrationEffect.aidl
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2017 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;
+
+parcelable VibrationEffect;
+parcelable VibrationEffect.OneShotVibration;
+parcelable VibrationEffect.WaveformVibration;
+parcelable VibrationEffect.EffectVibration;
diff --git a/core/java/android/os/VibrationEffect.java b/core/java/android/os/VibrationEffect.java
new file mode 100644
index 000000000000..eceaa31b9cf8
--- /dev/null
+++ b/core/java/android/os/VibrationEffect.java
@@ -0,0 +1,444 @@
+/*
+ * Copyright (C) 2017 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;
+
+import android.hardware.vibrator.V1_0.Constants.Effect;
+
+import java.util.Arrays;
+
+/**
+ * A VibrationEffect describes a haptic effect to be performed by a {@link Vibrator}.
+ *
+ * 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;
+
+ /**
+ * The default vibration strength of the device.
+ */
+ public static final int DEFAULT_AMPLITUDE = -1;
+
+ /**
+ * A click effect.
+ *
+ * @see #get(int)
+ * @hide
+ */
+ public static final int EFFECT_CLICK = Effect.CLICK;
+
+ /**
+ * A double click effect.
+ *
+ * @see #get(int)
+ * @hide
+ */
+ public static final int EFFECT_DOUBLE_CLICK = Effect.DOUBLE_CLICK;
+
+ /** @hide to prevent subclassing from outside of the framework */
+ public VibrationEffect() { }
+
+ /**
+ * Create a one shot vibration.
+ *
+ * One shot vibrations will vibrate constantly for the specified period of time at the
+ * specified amplitude, and then stop.
+ *
+ * @param milliseconds The number of milliseconds to vibrate. This must be a positive number.
+ * @param amplitude The strength of the vibration. This must be a value between 1 and 255, or
+ * {@link #DEFAULT_AMPLITUDE}.
+ *
+ * @return The desired effect.
+ */
+ public static VibrationEffect createOneShot(long milliseconds, int amplitude) {
+ VibrationEffect effect = new OneShot(milliseconds, amplitude);
+ effect.validate();
+ return effect;
+ }
+
+ /**
+ * Create a waveform vibration.
+ *
+ * Waveform vibrations are a potentially repeating series of timing and amplitude pairs. For
+ * each pair, the value in the amplitude array determines the strength of the vibration and the
+ * value in the timing array determines how long it vibrates for. An amplitude of 0 implies no
+ * vibration (i.e. off), and any pairs with a timing value of 0 will be ignored.
+ * <p>
+ * The amplitude array of the generated waveform will be the same size as the given
+ * timing array with alternating values of 0 (i.e. off) and {@link #DEFAULT_AMPLITUDE},
+ * starting with 0. Therefore the first timing value will be the period to wait before turning
+ * the vibrator on, the second value will be how long to vibrate at {@link #DEFAULT_AMPLITUDE}
+ * strength, etc.
+ * </p><p>
+ * To cause the pattern to repeat, pass the index into the timings array at which to start the
+ * repetition, or -1 to disable repeating.
+ * </p>
+ *
+ * @param timings The pattern of alternating on-off timings, starting with off. Timing values
+ * of 0 will cause the timing / amplitude pair to be ignored.
+ * @param repeat The index into the timings array at which to repeat, or -1 if you you don't
+ * want to repeat.
+ *
+ * @return The desired effect.
+ */
+ public static VibrationEffect createWaveform(long[] timings, int repeat) {
+ int[] amplitudes = new int[timings.length];
+ for (int i = 0; i < (timings.length / 2); i++) {
+ amplitudes[i*2 + 1] = VibrationEffect.DEFAULT_AMPLITUDE;
+ }
+ return createWaveform(timings, amplitudes, repeat);
+ }
+
+ /**
+ * Create a waveform vibration.
+ *
+ * Waveform vibrations are a potentially repeating series of timing and amplitude pairs. For
+ * each pair, the value in the amplitude array determines the strength of the vibration and the
+ * value in the timing array determines how long it vibrates for. An amplitude of 0 implies no
+ * vibration (i.e. off), and any pairs with a timing value of 0 will be ignored.
+ * </p><p>
+ * To cause the pattern to repeat, pass the index into the timings array at which to start the
+ * repetition, or -1 to disable repeating.
+ * </p>
+ *
+ * @param timings The timing values of the timing / amplitude pairs. Timing values of 0
+ * will cause the pair to be ignored.
+ * @param amplitudes The amplitude values of the timing / amplitude pairs. Amplitude values
+ * must be between 0 and 255, or equal to {@link #DEFAULT_AMPLITUDE}. An
+ * amplitude value of 0 implies the motor is off.
+ * @param repeat The index into the timings array at which to repeat, or -1 if you you don't
+ * want to repeat.
+ *
+ * @return The desired effect.
+ */
+ public static VibrationEffect createWaveform(long[] timings, int[] amplitudes, int repeat) {
+ VibrationEffect effect = new Waveform(timings, amplitudes, repeat);
+ effect.validate();
+ return effect;
+ }
+
+ /**
+ * Get a predefined vibration effect.
+ *
+ * Predefined effects are a set of common vibration effects that should be identical, regardless
+ * of the app they come from, in order to provide a cohesive experience for users across
+ * the entire device. They also may be custom tailored to the device hardware in order to
+ * provide a better experience than you could otherwise build using the generic building
+ * blocks.
+ *
+ * @param effectId The ID of the effect to perform:
+ * {@link #EFFECT_CLICK}, {@link #EFFECT_DOUBLE_CLICK}.
+ *
+ * @return The desired effect.
+ * @hide
+ */
+ public static VibrationEffect get(int effectId) {
+ VibrationEffect effect = new Prebaked(effectId);
+ effect.validate();
+ return effect;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ /** @hide */
+ public abstract void validate();
+
+ /** @hide */
+ public static class OneShot extends VibrationEffect implements Parcelable {
+ private long mTiming;
+ private int mAmplitude;
+
+ public OneShot(Parcel in) {
+ this(in.readLong(), in.readInt());
+ }
+
+ public OneShot(long milliseconds, int amplitude) {
+ mTiming = milliseconds;
+ mAmplitude = amplitude;
+ }
+
+ public long getTiming() {
+ return mTiming;
+ }
+
+ public int getAmplitude() {
+ return mAmplitude;
+ }
+
+ @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");
+ }
+ if (mTiming <= 0) {
+ throw new IllegalArgumentException("timing must be positive");
+ }
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (!(o instanceof VibrationEffect.OneShot)) {
+ return false;
+ }
+ VibrationEffect.OneShot other = (VibrationEffect.OneShot) o;
+ return other.mTiming == mTiming && other.mAmplitude == mAmplitude;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = 17;
+ result = 37 * (int) mTiming;
+ result = 37 * mAmplitude;
+ return result;
+ }
+
+ @Override
+ public String toString() {
+ return "OneShot{mTiming=" + mTiming +", mAmplitude=" + mAmplitude + "}";
+ }
+
+ @Override
+ public void writeToParcel(Parcel out, int flags) {
+ out.writeInt(PARCEL_TOKEN_ONE_SHOT);
+ out.writeLong(mTiming);
+ out.writeInt(mAmplitude);
+ }
+
+ public static final 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 */
+ public static class Waveform extends VibrationEffect implements Parcelable {
+ private long[] mTimings;
+ private int[] mAmplitudes;
+ private 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;
+ }
+
+ public int getRepeatIndex() {
+ return mRepeat;
+ }
+
+ @Override
+ public void validate() {
+ if (mTimings.length != mAmplitudes.length) {
+ throw new IllegalArgumentException(
+ "timing and amplitude arrays must be of equal length");
+ }
+ if (!hasNonZeroEntry(mTimings)) {
+ throw new IllegalArgumentException("at least one timing must be non-zero");
+ }
+ for (long timing : mTimings) {
+ if (timing < 0) {
+ throw new IllegalArgumentException("timings must all be >= 0");
+ }
+ }
+ for (int amplitude : mAmplitudes) {
+ if (amplitude < -1 || amplitude > 255) {
+ throw new IllegalArgumentException(
+ "amplitudes must all be DEFAULT_AMPLITUDE or between 0 and 255");
+ }
+ }
+ if (mRepeat < -1 || mRepeat >= mTimings.length) {
+ throw new IllegalArgumentException("repeat index must be >= -1");
+ }
+ }
+
+ @Override
+ public boolean equals(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 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 */
+ public static class Prebaked extends VibrationEffect implements Parcelable {
+ private int mEffectId;
+
+ public Prebaked(Parcel in) {
+ this(in.readInt());
+ }
+
+ public Prebaked(int effectId) {
+ mEffectId = effectId;
+ }
+
+ public int getId() {
+ return mEffectId;
+ }
+
+ @Override
+ public void validate() {
+ if (mEffectId != EFFECT_CLICK) {
+ throw new IllegalArgumentException("Unknown prebaked effect type");
+ }
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (!(o instanceof VibrationEffect.Prebaked)) {
+ return false;
+ }
+ VibrationEffect.Prebaked other = (VibrationEffect.Prebaked) o;
+ return mEffectId == other.mEffectId;
+ }
+
+ @Override
+ public int hashCode() {
+ return mEffectId;
+ }
+
+ @Override
+ public String toString() {
+ return "Prebaked{mEffectId=" + mEffectId + "}";
+ }
+
+
+ @Override
+ public void writeToParcel(Parcel out, int flags) {
+ out.writeInt(PARCEL_TOKEN_EFFECT);
+ out.writeInt(mEffectId);
+ }
+
+ public static final 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];
+ }
+ };
+ }
+
+ 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 {
+ throw new IllegalStateException(
+ "Unexpected vibration event type token in parcel.");
+ }
+ }
+ @Override
+ public VibrationEffect[] newArray(int size) {
+ return new VibrationEffect[size];
+ }
+ };
+}
diff --git a/core/java/android/os/Vibrator.java b/core/java/android/os/Vibrator.java
index f9b7666b79f7..b1f64218b7b4 100644
--- a/core/java/android/os/Vibrator.java
+++ b/core/java/android/os/Vibrator.java
@@ -55,12 +55,22 @@ public abstract class Vibrator {
public abstract boolean hasVibrator();
/**
+ * Check whether the vibrator has amplitude control.
+ *
+ * @return True if the hardware can control the amplitude of the vibrations, otherwise false.
+ */
+ public abstract boolean hasAmplitudeControl();
+
+ /**
* Vibrate constantly for the specified period of time.
* <p>This method requires the caller to hold the permission
* {@link android.Manifest.permission#VIBRATE}.
*
* @param milliseconds The number of milliseconds to vibrate.
+ *
+ * @deprecated Use {@link #vibrate(VibrationEffect)} instead.
*/
+ @Deprecated
public void vibrate(long milliseconds) {
vibrate(milliseconds, null);
}
@@ -75,9 +85,14 @@ public abstract class Vibrator {
* specify {@link AudioAttributes#USAGE_ALARM} for alarm vibrations or
* {@link AudioAttributes#USAGE_NOTIFICATION_RINGTONE} for
* vibrations associated with incoming calls.
+ *
+ * @deprecated Use {@link #vibrate(VibrationEffect, AudioAttributes)} instead.
*/
+ @Deprecated
public void vibrate(long milliseconds, AudioAttributes attributes) {
- vibrate(Process.myUid(), mPackageName, milliseconds, attributes);
+ VibrationEffect effect =
+ VibrationEffect.createOneShot(milliseconds, VibrationEffect.DEFAULT_AMPLITUDE);
+ vibrate(effect, attributes);
}
/**
@@ -99,7 +114,10 @@ public abstract class Vibrator {
* @param pattern an array of longs of times for which to turn the vibrator on or off.
* @param repeat the index into pattern at which to repeat, or -1 if
* you don't want to repeat.
+ *
+ * @deprecated Use {@link #vibrate(VibrationEffect)} instead.
*/
+ @Deprecated
public void vibrate(long[] pattern, int repeat) {
vibrate(pattern, repeat, null);
}
@@ -127,26 +145,34 @@ public abstract class Vibrator {
* specify {@link AudioAttributes#USAGE_ALARM} for alarm vibrations or
* {@link AudioAttributes#USAGE_NOTIFICATION_RINGTONE} for
* vibrations associated with incoming calls.
+ *
+ * @deprecated Use {@link #vibrate(VibrationEffect, AudioAttributes)} instead.
*/
+ @Deprecated
public void vibrate(long[] pattern, int repeat, AudioAttributes attributes) {
- vibrate(Process.myUid(), mPackageName, pattern, repeat, attributes);
+ // This call needs to continue throwing ArrayIndexOutOfBoundsException for compatibility
+ // purposes, whereas VibrationEffect throws an IllegalArgumentException.
+ if (repeat < -1 || repeat >= pattern.length) {
+ throw new ArrayIndexOutOfBoundsException();
+ }
+ vibrate(VibrationEffect.createWaveform(pattern, repeat), attributes);
}
- /**
- * @hide
- * Like {@link #vibrate(long, AudioAttributes)}, but allowing the caller to specify that
- * the vibration is owned by someone else.
- */
- public abstract void vibrate(int uid, String opPkg, long milliseconds,
- AudioAttributes attributes);
+ public void vibrate(VibrationEffect vibe) {
+ vibrate(vibe, null);
+ }
+
+ public void vibrate(VibrationEffect vibe, AudioAttributes attributes) {
+ vibrate(Process.myUid(), mPackageName, vibe, attributes);
+ }
/**
+ * Like {@link #vibrate(VibrationEffect, AudioAttributes)}, but allowing the caller to specify
+ * that the vibration is owned by someone else.
* @hide
- * Like {@link #vibrate(long[], int, AudioAttributes)}, but allowing the caller to specify that
- * the vibration is owned by someone else.
*/
- public abstract void vibrate(int uid, String opPkg, long[] pattern, int repeat,
- AudioAttributes attributes);
+ public abstract void vibrate(int uid, String opPkg,
+ VibrationEffect vibe, AudioAttributes attributes);
/**
* Turn the vibrator off.
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 385f256f5d1c..a35705b0315c 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -2498,6 +2498,9 @@
<!-- How long history of previous vibrations should be kept for the dumpsys. -->
<integer name="config_previousVibrationsDumpLimit">20</integer>
+ <!-- The default vibration strength, must be between 1 and 255 inclusive. -->
+ <integer name="config_defaultVibrationAmplitude">255</integer>
+
<!-- Number of retries Cell Data should attempt for a given error code before
restarting the modem.
Error codes not listed will not lead to modem restarts.
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 0d4a407db242..359a9474de85 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -1816,6 +1816,7 @@
<java-symbol type="integer" name="config_notificationsBatteryMediumARGB" />
<java-symbol type="integer" name="config_notificationServiceArchiveSize" />
<java-symbol type="integer" name="config_previousVibrationsDumpLimit" />
+ <java-symbol type="integer" name="config_defaultVibrationAmplitude" />
<java-symbol type="integer" name="config_radioScanningTimeout" />
<java-symbol type="integer" name="config_screenBrightnessSettingMinimum" />
<java-symbol type="integer" name="config_screenBrightnessSettingMaximum" />
diff --git a/services/core/Android.mk b/services/core/Android.mk
index 794ece65b03d..d312902382f9 100644
--- a/services/core/Android.mk
+++ b/services/core/Android.mk
@@ -28,6 +28,7 @@ LOCAL_STATIC_JAVA_LIBRARIES := \
tzdata_update2 \
android.hidl.base@1.0-java-static \
android.hardware.biometrics.fingerprint@2.1-java-static \
+ android.hardware.vibrator@1.0-java-constants \
ifneq ($(INCREMENTAL_BUILDS),)
LOCAL_PROGUARD_ENABLED := disabled
diff --git a/services/core/java/com/android/server/VibratorService.java b/services/core/java/com/android/server/VibratorService.java
index 5fe6952fbc66..c4676d12c8bd 100644
--- a/services/core/java/com/android/server/VibratorService.java
+++ b/services/core/java/com/android/server/VibratorService.java
@@ -22,8 +22,10 @@ import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
+import android.content.res.Resources;
import android.database.ContentObserver;
import android.hardware.input.InputManager;
+import android.hardware.vibrator.V1_0.Constants.EffectStrength;
import android.media.AudioManager;
import android.os.PowerSaveState;
import android.os.BatteryStats;
@@ -42,6 +44,7 @@ import android.os.ShellCommand;
import android.os.SystemClock;
import android.os.UserHandle;
import android.os.Vibrator;
+import android.os.VibrationEffect;
import android.os.WorkSource;
import android.provider.Settings;
import android.provider.Settings.SettingNotFoundException;
@@ -67,12 +70,14 @@ public class VibratorService extends IVibratorService.Stub
private static final boolean DEBUG = false;
private static final String SYSTEM_UI_PACKAGE = "com.android.systemui";
- private final LinkedList<Vibration> mVibrations;
private final LinkedList<VibrationInfo> mPreviousVibrations;
private final int mPreviousVibrationsLimit;
- private Vibration mCurrentVibration;
+ private final boolean mSupportsAmplitudeControl;
+ private final int mDefaultVibrationAmplitude;
+ private final VibrationEffect[] mFallbackEffects;
private final WorkSource mTmpWorkSource = new WorkSource();
private final Handler mH = new Handler();
+ private final Object mLock = new Object();
private final Context mContext;
private final PowerManager.WakeLock mWakeLock;
@@ -81,14 +86,15 @@ public class VibratorService extends IVibratorService.Stub
private PowerManagerInternal mPowerManagerInternal;
private InputManager mIm;
- volatile VibrateThread mThread;
+ private volatile VibrateThread mThread;
- // mInputDeviceVibrators lock should be acquired after mVibrations lock, if both are
+ // mInputDeviceVibrators lock should be acquired after mLock, if both are
// to be acquired
private final ArrayList<Vibrator> mInputDeviceVibrators = new ArrayList<Vibrator>();
private boolean mVibrateInputDevicesSetting; // guarded by mInputDeviceVibrators
private boolean mInputDeviceListenerRegistered; // guarded by mInputDeviceVibrators
+ private Vibration mCurrentVibration;
private int mCurVibUid = -1;
private boolean mLowPowerMode;
private SettingsObserver mSettingObserver;
@@ -97,106 +103,87 @@ public class VibratorService extends IVibratorService.Stub
native static void vibratorInit();
native static void vibratorOn(long milliseconds);
native static void vibratorOff();
+ native static boolean vibratorSupportsAmplitudeControl();
+ native static void vibratorSetAmplitude(int amplitude);
+ native static long vibratorPerformEffect(long effect, long strength);
private class Vibration implements IBinder.DeathRecipient {
private final IBinder mToken;
- private final long mTimeout;
- private final long mStartTime;
- private final long[] mPattern;
- private final int mRepeat;
- private final int mUsageHint;
- private final int mUid;
- private final String mOpPkg;
-
- Vibration(IBinder token, long millis, int usageHint, int uid, String opPkg) {
- this(token, millis, null, 0, usageHint, uid, opPkg);
- }
+ private final VibrationEffect mEffect;
+ private final long mStartTime;
+ private final int mUsageHint;
+ private final int mUid;
+ private final String mOpPkg;
- Vibration(IBinder token, long[] pattern, int repeat, int usageHint, int uid,
- String opPkg) {
- this(token, 0, pattern, repeat, usageHint, uid, opPkg);
- }
-
- private Vibration(IBinder token, long millis, long[] pattern,
- int repeat, int usageHint, int uid, String opPkg) {
+ private Vibration(IBinder token, VibrationEffect effect,
+ int usageHint, int uid, String opPkg) {
mToken = token;
- mTimeout = millis;
+ mEffect = effect;
mStartTime = SystemClock.uptimeMillis();
- mPattern = pattern;
- mRepeat = repeat;
mUsageHint = usageHint;
mUid = uid;
mOpPkg = opPkg;
}
public void binderDied() {
- synchronized (mVibrations) {
- mVibrations.remove(this);
+ synchronized (mLock) {
if (this == mCurrentVibration) {
doCancelVibrateLocked();
- startNextVibrationLocked();
}
}
}
public boolean hasLongerTimeout(long millis) {
- if (mTimeout == 0) {
- // This is a pattern, return false to play the simple
- // vibration.
- return false;
+ // If the current effect is a one shot vibration that will end after the given timeout
+ // for the new one shot vibration, then just let the current vibration finish. All
+ // other effect types will get pre-empted.
+ if (mEffect instanceof VibrationEffect.OneShot) {
+ VibrationEffect.OneShot oneShot = (VibrationEffect.OneShot) mEffect;
+ return mStartTime + oneShot.getTiming() > SystemClock.uptimeMillis() + millis;
}
- if ((mStartTime + mTimeout)
- < (SystemClock.uptimeMillis() + millis)) {
- // If this vibration will end before the time passed in, let
- // the new vibration play.
- return false;
- }
- return true;
+ return false;
}
public boolean isSystemHapticFeedback() {
+ boolean repeating = false;
+ if (mEffect instanceof VibrationEffect.Waveform) {
+ VibrationEffect.Waveform waveform = (VibrationEffect.Waveform) mEffect;
+ repeating = (waveform.getRepeatIndex() < 0);
+ }
return (mUid == Process.SYSTEM_UID || mUid == 0 || SYSTEM_UI_PACKAGE.equals(mOpPkg))
- && mRepeat < 0;
+ && !repeating;
}
}
private static class VibrationInfo {
- long timeout;
- long startTime;
- long[] pattern;
- int repeat;
- int usageHint;
- int uid;
- String opPkg;
-
- public VibrationInfo(long timeout, long startTime, long[] pattern, int repeat,
+ private final long mStartTime;
+ private final VibrationEffect mEffect;
+ private final int mUsageHint;
+ private final int mUid;
+ private final String mOpPkg;
+
+ public VibrationInfo(long startTime, VibrationEffect effect,
int usageHint, int uid, String opPkg) {
- this.timeout = timeout;
- this.startTime = startTime;
- this.pattern = pattern;
- this.repeat = repeat;
- this.usageHint = usageHint;
- this.uid = uid;
- this.opPkg = opPkg;
+ mStartTime = startTime;
+ mEffect = effect;
+ mUsageHint = usageHint;
+ mUid = uid;
+ mOpPkg = opPkg;
}
@Override
public String toString() {
return new StringBuilder()
- .append("timeout: ")
- .append(timeout)
.append(", startTime: ")
- .append(startTime)
- .append(", pattern: ")
- .append(Arrays.toString(pattern))
- .append(", repeat: ")
- .append(repeat)
+ .append(mStartTime)
+ .append(", effect: ")
+ .append(mEffect)
.append(", usageHint: ")
- .append(usageHint)
+ .append(mUsageHint)
.append(", uid: ")
- .append(uid)
+ .append(mUid)
.append(", opPkg: ")
- .append(opPkg)
+ .append(mOpPkg)
.toString();
}
}
@@ -207,25 +194,38 @@ public class VibratorService extends IVibratorService.Stub
// restart instead of a fresh boot.
vibratorOff();
+ mSupportsAmplitudeControl = vibratorSupportsAmplitudeControl();
+
mContext = context;
- PowerManager pm = (PowerManager)context.getSystemService(
- Context.POWER_SERVICE);
+ PowerManager pm = (PowerManager)context.getSystemService(Context.POWER_SERVICE);
mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "*vibrator*");
mWakeLock.setReferenceCounted(true);
- mAppOpsService = IAppOpsService.Stub.asInterface(ServiceManager.getService(Context.APP_OPS_SERVICE));
+ mAppOpsService =
+ IAppOpsService.Stub.asInterface(ServiceManager.getService(Context.APP_OPS_SERVICE));
mBatteryStatsService = IBatteryStats.Stub.asInterface(ServiceManager.getService(
BatteryStats.SERVICE_NAME));
mPreviousVibrationsLimit = mContext.getResources().getInteger(
com.android.internal.R.integer.config_previousVibrationsDumpLimit);
- mVibrations = new LinkedList<>();
+ mDefaultVibrationAmplitude = mContext.getResources().getInteger(
+ com.android.internal.R.integer.config_defaultVibrationAmplitude);
+
mPreviousVibrations = new LinkedList<>();
IntentFilter filter = new IntentFilter();
filter.addAction(Intent.ACTION_SCREEN_OFF);
context.registerReceiver(mIntentReceiver, filter);
+
+ long[] clickEffectTimings = getLongIntArray(context.getResources(),
+ com.android.internal.R.array.config_virtualKeyVibePattern);
+ VibrationEffect clickEffect = VibrationEffect.createWaveform(clickEffectTimings, -1);
+ VibrationEffect doubleClickEffect = VibrationEffect.createWaveform(
+ new long[] {0, 30, 100, 30} /*timings*/, -1);
+
+ mFallbackEffects = new VibrationEffect[] { clickEffect, doubleClickEffect };
+
}
public void systemReady() {
@@ -242,7 +242,7 @@ public class VibratorService extends IVibratorService.Stub
@Override
public void onLowPowerModeChanged(PowerSaveState result) {
- updateInputDeviceVibrators();
+ updateVibrators();
}
});
@@ -253,11 +253,11 @@ public class VibratorService extends IVibratorService.Stub
mContext.registerReceiver(new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
- updateInputDeviceVibrators();
+ updateVibrators();
}
}, new IntentFilter(Intent.ACTION_USER_SWITCHED), null, mH);
- updateInputDeviceVibrators();
+ updateVibrators();
}
private final class SettingsObserver extends ContentObserver {
@@ -267,7 +267,7 @@ public class VibratorService extends IVibratorService.Stub
@Override
public void onChange(boolean SelfChange) {
- updateInputDeviceVibrators();
+ updateVibrators();
}
}
@@ -276,6 +276,15 @@ public class VibratorService extends IVibratorService.Stub
return doVibratorExists();
}
+ @Override // Binder call
+ public boolean hasAmplitudeControl() {
+ synchronized (mInputDeviceVibrators) {
+ // Input device vibrators don't support amplitude controls yet, but are still used over
+ // the system vibrator when connected.
+ return mSupportsAmplitudeControl && mInputDeviceVibrators.isEmpty();
+ }
+ }
+
private void verifyIncomingUid(int uid) {
if (uid == Binder.getCallingUid()) {
return;
@@ -287,103 +296,96 @@ public class VibratorService extends IVibratorService.Stub
Binder.getCallingPid(), Binder.getCallingUid(), null);
}
- @Override // Binder call
- public void vibrate(int uid, String opPkg, long milliseconds, int usageHint,
- IBinder token) {
- if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.VIBRATE)
- != PackageManager.PERMISSION_GRANTED) {
- throw new SecurityException("Requires VIBRATE permission");
+ /**
+ * Validate the incoming VibrationEffect.
+ *
+ * We can't throw exceptions here since we might be called from some system_server component,
+ * which would bring the whole system down.
+ *
+ * @return whether the VibrationEffect is valid
+ */
+ private static boolean verifyVibrationEffect(VibrationEffect effect) {
+ if (effect == null) {
+ // Effect must not be null.
+ Slog.wtf(TAG, "effect must not be null");
+ return false;
}
- verifyIncomingUid(uid);
- // We're running in the system server so we cannot crash. Check for a
- // timeout of 0 or negative. This will ensure that a vibration has
- // either a timeout of > 0 or a non-null pattern.
- if (milliseconds <= 0 || (mCurrentVibration != null
- && mCurrentVibration.hasLongerTimeout(milliseconds))) {
- // Ignore this vibration since the current vibration will play for
- // longer than milliseconds.
- return;
- }
-
- if (DEBUG) {
- Slog.d(TAG, "Vibrating for " + milliseconds + " ms.");
- }
-
- Vibration vib = new Vibration(token, milliseconds, usageHint, uid, opPkg);
-
- final long ident = Binder.clearCallingIdentity();
try {
- synchronized (mVibrations) {
- removeVibrationLocked(token);
- doCancelVibrateLocked();
- addToPreviousVibrationsLocked(vib);
- startVibrationLocked(vib);
- }
- } finally {
- Binder.restoreCallingIdentity(ident);
+ effect.validate();
+ } catch (Exception e) {
+ Slog.wtf(TAG, "Encountered issue when verifying VibrationEffect.", e);
+ return false;
}
+ return true;
}
- private boolean isAll0(long[] pattern) {
- int N = pattern.length;
- for (int i = 0; i < N; i++) {
- if (pattern[i] != 0) {
- return false;
- }
+ private static long[] getLongIntArray(Resources r, int resid) {
+ int[] ar = r.getIntArray(resid);
+ if (ar == null) {
+ return null;
}
- return true;
+ long[] out = new long[ar.length];
+ for (int i = 0; i < ar.length; i++) {
+ out[i] = ar[i];
+ }
+ return out;
}
@Override // Binder call
- public void vibratePattern(int uid, String packageName, long[] pattern, int repeat,
- int usageHint, IBinder token) {
+ public void vibrate(int uid, String opPkg, VibrationEffect effect, int usageHint,
+ IBinder token) {
if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.VIBRATE)
!= PackageManager.PERMISSION_GRANTED) {
throw new SecurityException("Requires VIBRATE permission");
}
+ if (token == null) {
+ Slog.e(TAG, "token must not be null");
+ return;
+ }
verifyIncomingUid(uid);
- // so wakelock calls will succeed
- long identity = Binder.clearCallingIdentity();
- try {
- if (DEBUG) {
- String s = "";
- int N = pattern.length;
- for (int i=0; i<N; i++) {
- s += " " + pattern[i];
- }
- Slog.d(TAG, "Vibrating with pattern:" + s);
- }
+ if (!verifyVibrationEffect(effect)) {
+ return;
+ }
- // we're running in the server so we can't fail
- if (pattern == null || pattern.length == 0
- || isAll0(pattern)
- || repeat >= pattern.length || token == null) {
+ // If our current vibration is longer than the new vibration and is the same amplitude,
+ // then just let the current one finish.
+ if (effect instanceof VibrationEffect.OneShot
+ && mCurrentVibration != null
+ && mCurrentVibration.mEffect instanceof VibrationEffect.OneShot) {
+ VibrationEffect.OneShot newOneShot = (VibrationEffect.OneShot) effect;
+ VibrationEffect.OneShot currentOneShot =
+ (VibrationEffect.OneShot) mCurrentVibration.mEffect;
+ if (mCurrentVibration.hasLongerTimeout(newOneShot.getTiming())
+ && newOneShot.getAmplitude() == currentOneShot.getAmplitude()) {
+ if (DEBUG) {
+ Slog.e(TAG, "Ignoring incoming vibration in favor of current vibration");
+ }
return;
}
+ }
+
+ Vibration vib = new Vibration(token, effect, usageHint, uid, opPkg);
- Vibration vib = new Vibration(token, pattern, repeat, usageHint, uid, packageName);
+ // Only link against waveforms since they potentially don't have a finish if
+ // they're repeating. Let other effects just play out until they're done.
+ if (effect instanceof VibrationEffect.Waveform) {
try {
token.linkToDeath(vib, 0);
} catch (RemoteException e) {
return;
}
+ }
- synchronized (mVibrations) {
- removeVibrationLocked(token);
+
+ long ident = Binder.clearCallingIdentity();
+ try {
+ synchronized (mLock) {
doCancelVibrateLocked();
- if (repeat >= 0) {
- mVibrations.addFirst(vib);
- startNextVibrationLocked();
- } else {
- // A negative repeat means that this pattern is not meant
- // to repeat. Treat it like a simple vibration.
- startVibrationLocked(vib);
- }
+ startVibrationLocked(vib);
addToPreviousVibrationsLocked(vib);
}
- }
- finally {
- Binder.restoreCallingIdentity(identity);
+ } finally {
+ Binder.restoreCallingIdentity(ident);
}
}
@@ -391,8 +393,8 @@ public class VibratorService extends IVibratorService.Stub
if (mPreviousVibrations.size() > mPreviousVibrationsLimit) {
mPreviousVibrations.removeFirst();
}
- mPreviousVibrations.addLast(new VibratorService.VibrationInfo(vib.mTimeout, vib.mStartTime,
- vib.mPattern, vib.mRepeat, vib.mUsageHint, vib.mUid, vib.mOpPkg));
+ mPreviousVibrations.addLast(new VibrationInfo(
+ vib.mStartTime, vib.mEffect, vib.mUsageHint, vib.mUid, vib.mOpPkg));
}
@Override // Binder call
@@ -401,97 +403,97 @@ public class VibratorService extends IVibratorService.Stub
android.Manifest.permission.VIBRATE,
"cancelVibrate");
- // so wakelock calls will succeed
- long identity = Binder.clearCallingIdentity();
- try {
- synchronized (mVibrations) {
- final Vibration vib = removeVibrationLocked(token);
- if (vib == mCurrentVibration) {
- if (DEBUG) {
- Slog.d(TAG, "Canceling vibration.");
- }
+ synchronized (mLock) {
+ if (mCurrentVibration != null && mCurrentVibration.mToken == token) {
+ if (DEBUG) {
+ Slog.d(TAG, "Canceling vibration.");
+ }
+ long ident = Binder.clearCallingIdentity();
+ try {
doCancelVibrateLocked();
- startNextVibrationLocked();
+ } finally {
+ Binder.restoreCallingIdentity(ident);
}
}
}
- finally {
- Binder.restoreCallingIdentity(identity);
- }
}
- private final Runnable mVibrationRunnable = new Runnable() {
+ private final Runnable mVibrationEndRunnable = new Runnable() {
@Override
public void run() {
- synchronized (mVibrations) {
- doCancelVibrateLocked();
- startNextVibrationLocked();
- }
+ onVibrationFinished();
}
};
- // Lock held on mVibrations
private void doCancelVibrateLocked() {
+ mH.removeCallbacks(mVibrationEndRunnable);
if (mThread != null) {
- synchronized (mThread) {
- mThread.mDone = true;
- mThread.notify();
- }
+ mThread.cancel();
mThread = null;
}
doVibratorOff();
- mH.removeCallbacks(mVibrationRunnable);
reportFinishVibrationLocked();
}
- // Lock held on mVibrations
- private void startNextVibrationLocked() {
- if (mVibrations.size() <= 0) {
- reportFinishVibrationLocked();
- mCurrentVibration = null;
- return;
+ // Callback for whenever the current vibration has finished played out
+ public void onVibrationFinished() {
+ if (DEBUG) {
+ Slog.e(TAG, "Vibration finished, cleaning up");
+ }
+ synchronized (mLock) {
+ // Make sure the vibration is really done. This also reports that the vibration is
+ // finished.
+ doCancelVibrateLocked();
}
- startVibrationLocked(mVibrations.getFirst());
}
- // Lock held on mVibrations
private void startVibrationLocked(final Vibration vib) {
- try {
- if (mLowPowerMode
- && vib.mUsageHint != AudioAttributes.USAGE_NOTIFICATION_RINGTONE) {
- return;
+ if (mLowPowerMode && vib.mUsageHint != AudioAttributes.USAGE_NOTIFICATION_RINGTONE) {
+ if (DEBUG) {
+ Slog.e(TAG, "Vibrate ignored, low power mode");
}
+ return;
+ }
- if (vib.mUsageHint == AudioAttributes.USAGE_NOTIFICATION_RINGTONE &&
- !shouldVibrateForRingtone()) {
- return;
+ if (vib.mUsageHint == AudioAttributes.USAGE_NOTIFICATION_RINGTONE &&
+ !shouldVibrateForRingtone()) {
+ if (DEBUG) {
+ Slog.e(TAG, "Vibrate ignored, not vibrating for ringtones");
}
+ return;
+ }
- int mode = mAppOpsService.checkAudioOperation(AppOpsManager.OP_VIBRATE,
- vib.mUsageHint, vib.mUid, vib.mOpPkg);
- if (mode == AppOpsManager.MODE_ALLOWED) {
- mode = mAppOpsService.startOperation(AppOpsManager.getToken(mAppOpsService),
- AppOpsManager.OP_VIBRATE, vib.mUid, vib.mOpPkg);
+ final int mode = getAppOpMode(vib);
+ if (mode != AppOpsManager.MODE_ALLOWED) {
+ if (mode == AppOpsManager.MODE_ERRORED) {
+ // We might be getting calls from within system_server, so we don't actually want
+ // to throw a SecurityException here.
+ Slog.w(TAG, "Would be an error: vibrate from uid " + vib.mUid);
}
- if (mode == AppOpsManager.MODE_ALLOWED) {
- mCurrentVibration = vib;
- } else {
- if (mode == AppOpsManager.MODE_ERRORED) {
- Slog.w(TAG, "Would be an error: vibrate from uid " + vib.mUid);
- }
- mH.post(mVibrationRunnable);
- return;
- }
- } catch (RemoteException e) {
+ return;
}
- if (vib.mTimeout != 0) {
- doVibratorOn(vib.mTimeout, vib.mUid, vib.mUsageHint);
- mH.postDelayed(mVibrationRunnable, vib.mTimeout);
- } else {
+ startVibrationInnerLocked(vib);
+ }
+
+ private void startVibrationInnerLocked(Vibration vib) {
+ mCurrentVibration = vib;
+ if (vib.mEffect instanceof VibrationEffect.OneShot) {
+ VibrationEffect.OneShot oneShot = (VibrationEffect.OneShot) vib.mEffect;
+ doVibratorOn(oneShot.getTiming(), oneShot.getAmplitude(), vib.mUid, vib.mUsageHint);
+ mH.postDelayed(mVibrationEndRunnable, oneShot.getTiming());
+ } else if (vib.mEffect instanceof VibrationEffect.Waveform) {
// mThread better be null here. doCancelVibrate should always be
// called before startNextVibrationLocked or startVibrationLocked.
- mThread = new VibrateThread(vib);
+ VibrationEffect.Waveform waveform = (VibrationEffect.Waveform) vib.mEffect;
+ mThread = new VibrateThread(waveform, vib.mUid, vib.mUsageHint);
mThread.start();
+ } else if (vib.mEffect instanceof VibrationEffect.Prebaked) {
+ long timeout = doVibratorPrebakedEffectLocked(vib);
+ if (timeout > 0) {
+ mH.postDelayed(mVibrationEndRunnable, timeout);
+ }
+ } else {
+ Slog.e(TAG, "Unknown vibration type, ignoring");
}
}
@@ -507,104 +509,115 @@ public class VibratorService extends IVibratorService.Stub
}
}
+ private int getAppOpMode(Vibration vib) {
+ int mode;
+ try {
+ mode = mAppOpsService.checkAudioOperation(AppOpsManager.OP_VIBRATE,
+ vib.mUsageHint, vib.mUid, vib.mOpPkg);
+ if (mode == AppOpsManager.MODE_ALLOWED) {
+ mode = mAppOpsService.startOperation(AppOpsManager.getToken(mAppOpsService),
+ AppOpsManager.OP_VIBRATE, vib.mUid, vib.mOpPkg);
+ }
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Failed to get appop mode for vibration!", e);
+ mode = AppOpsManager.MODE_IGNORED;
+ }
+ return mode;
+ }
+
private void reportFinishVibrationLocked() {
if (mCurrentVibration != null) {
try {
mAppOpsService.finishOperation(AppOpsManager.getToken(mAppOpsService),
AppOpsManager.OP_VIBRATE, mCurrentVibration.mUid,
mCurrentVibration.mOpPkg);
- } catch (RemoteException e) {
- }
+ } catch (RemoteException e) { }
mCurrentVibration = null;
}
}
- // Lock held on mVibrations
- private Vibration removeVibrationLocked(IBinder token) {
- ListIterator<Vibration> iter = mVibrations.listIterator(0);
- while (iter.hasNext()) {
- Vibration vib = iter.next();
- if (vib.mToken == token) {
- iter.remove();
- unlinkVibration(vib);
- return vib;
- }
- }
- // We might be looking for a simple vibration which is only stored in
- // mCurrentVibration.
- if (mCurrentVibration != null && mCurrentVibration.mToken == token) {
- unlinkVibration(mCurrentVibration);
- return mCurrentVibration;
- }
- return null;
- }
-
private void unlinkVibration(Vibration vib) {
- if (vib.mPattern != null) {
- // If Vibration object has a pattern,
- // the Vibration object has also been linkedToDeath.
+ if (vib.mEffect instanceof VibrationEffect.Waveform) {
vib.mToken.unlinkToDeath(vib, 0);
}
}
- private void updateInputDeviceVibrators() {
- synchronized (mVibrations) {
- doCancelVibrateLocked();
+ private void updateVibrators() {
+ synchronized (mLock) {
+ boolean devicesUpdated = updateInputDeviceVibratorsLocked();
+ boolean lowPowerModeUpdated = updateLowPowerModeLocked();
- synchronized (mInputDeviceVibrators) {
- mVibrateInputDevicesSetting = false;
- try {
- mVibrateInputDevicesSetting = Settings.System.getIntForUser(
- mContext.getContentResolver(),
- Settings.System.VIBRATE_INPUT_DEVICES, UserHandle.USER_CURRENT) > 0;
- } catch (SettingNotFoundException snfe) {
- }
+ if (devicesUpdated || lowPowerModeUpdated) {
+ // If the state changes out from under us then just reset.
+ doCancelVibrateLocked();
+ }
+ }
+ }
- mLowPowerMode = mPowerManagerInternal
- .getLowPowerState(ServiceType.VIBRATION).batterySaverEnabled;
+ private boolean updateInputDeviceVibratorsLocked() {
+ boolean changed = false;
+ boolean vibrateInputDevices = false;
+ try {
+ vibrateInputDevices = Settings.System.getIntForUser(
+ mContext.getContentResolver(),
+ Settings.System.VIBRATE_INPUT_DEVICES, UserHandle.USER_CURRENT) > 0;
+ } catch (SettingNotFoundException snfe) {
+ }
+ if (vibrateInputDevices != mVibrateInputDevicesSetting) {
+ changed = true;
+ mVibrateInputDevicesSetting = vibrateInputDevices;
+ }
- if (mVibrateInputDevicesSetting) {
- if (!mInputDeviceListenerRegistered) {
- mInputDeviceListenerRegistered = true;
- mIm.registerInputDeviceListener(this, mH);
- }
- } else {
- if (mInputDeviceListenerRegistered) {
- mInputDeviceListenerRegistered = false;
- mIm.unregisterInputDeviceListener(this);
- }
- }
+ if (mVibrateInputDevicesSetting) {
+ if (!mInputDeviceListenerRegistered) {
+ mInputDeviceListenerRegistered = true;
+ mIm.registerInputDeviceListener(this, mH);
+ }
+ } else {
+ if (mInputDeviceListenerRegistered) {
+ mInputDeviceListenerRegistered = false;
+ mIm.unregisterInputDeviceListener(this);
+ }
+ }
- mInputDeviceVibrators.clear();
- if (mVibrateInputDevicesSetting) {
- int[] ids = mIm.getInputDeviceIds();
- for (int i = 0; i < ids.length; i++) {
- InputDevice device = mIm.getInputDevice(ids[i]);
- Vibrator vibrator = device.getVibrator();
- if (vibrator.hasVibrator()) {
- mInputDeviceVibrators.add(vibrator);
- }
- }
+ mInputDeviceVibrators.clear();
+ if (mVibrateInputDevicesSetting) {
+ int[] ids = mIm.getInputDeviceIds();
+ for (int i = 0; i < ids.length; i++) {
+ InputDevice device = mIm.getInputDevice(ids[i]);
+ Vibrator vibrator = device.getVibrator();
+ if (vibrator.hasVibrator()) {
+ mInputDeviceVibrators.add(vibrator);
}
}
+ return true;
+ }
+ return changed;
+ }
- startNextVibrationLocked();
+ private boolean updateLowPowerModeLocked() {
+ boolean lowPowerMode = mPowerManagerInternal
+ .getLowPowerState(ServiceType.VIBRATION).batterySaverEnabled;
+ if (lowPowerMode != mLowPowerMode) {
+ mLowPowerMode = lowPowerMode;
+ return true;
}
+ return false;
}
@Override
public void onInputDeviceAdded(int deviceId) {
- updateInputDeviceVibrators();
+ updateVibrators();
}
@Override
public void onInputDeviceChanged(int deviceId) {
- updateInputDeviceVibrators();
+ updateVibrators();
}
@Override
public void onInputDeviceRemoved(int deviceId) {
- updateInputDeviceVibrators();
+ updateVibrators();
}
private boolean doVibratorExists() {
@@ -619,41 +632,44 @@ public class VibratorService extends IVibratorService.Stub
return vibratorExists();
}
- private void doVibratorOn(long millis, int uid, int usageHint) {
+ private void doVibratorOn(long millis, int amplitude, int uid, int usageHint) {
synchronized (mInputDeviceVibrators) {
- if (DEBUG) {
- Slog.d(TAG, "Turning vibrator on for " + millis + " ms.");
+ if (amplitude == VibrationEffect.DEFAULT_AMPLITUDE) {
+ amplitude = mDefaultVibrationAmplitude;
}
- try {
- mBatteryStatsService.noteVibratorOn(uid, millis);
- mCurVibUid = uid;
- } catch (RemoteException e) {
+ if (DEBUG) {
+ Slog.d(TAG, "Turning vibrator on for " + millis + " ms" +
+ " with amplitude " + amplitude + ".");
}
+ noteVibratorOnLocked(uid, millis);
final int vibratorCount = mInputDeviceVibrators.size();
if (vibratorCount != 0) {
- final AudioAttributes attributes = new AudioAttributes.Builder().setUsage(usageHint)
- .build();
+ final AudioAttributes attributes =
+ new AudioAttributes.Builder().setUsage(usageHint).build();
for (int i = 0; i < vibratorCount; i++) {
mInputDeviceVibrators.get(i).vibrate(millis, attributes);
}
} else {
+ // Note: ordering is important here! Many haptic drivers will reset their amplitude
+ // when enabled, so we always have to enable frst, then set the amplitude.
vibratorOn(millis);
+ doVibratorSetAmplitude(amplitude);
}
}
}
+ private void doVibratorSetAmplitude(int amplitude) {
+ if (mSupportsAmplitudeControl) {
+ vibratorSetAmplitude(amplitude);
+ }
+ }
+
private void doVibratorOff() {
synchronized (mInputDeviceVibrators) {
if (DEBUG) {
Slog.d(TAG, "Turning vibrator off.");
}
- if (mCurVibUid >= 0) {
- try {
- mBatteryStatsService.noteVibratorOff(mCurVibUid);
- } catch (RemoteException e) {
- }
- mCurVibUid = -1;
- }
+ noteVibratorOffLocked();
final int vibratorCount = mInputDeviceVibrators.size();
if (vibratorCount != 0) {
for (int i = 0; i < vibratorCount; i++) {
@@ -665,86 +681,175 @@ public class VibratorService extends IVibratorService.Stub
}
}
+ private long doVibratorPrebakedEffectLocked(Vibration vib) {
+ synchronized (mInputDeviceVibrators) {
+ VibrationEffect.Prebaked prebaked = (VibrationEffect.Prebaked) vib.mEffect;
+ // Input devices don't support prebaked effect, so skip trying it with them.
+ final int vibratorCount = mInputDeviceVibrators.size();
+ if (vibratorCount == 0) {
+ long timeout = vibratorPerformEffect(prebaked.getId(), EffectStrength.MEDIUM);
+ if (timeout > 0) {
+ noteVibratorOnLocked(vib.mUid, timeout);
+ return timeout;
+ }
+ }
+ final int id = prebaked.getId();
+ if (id < 0 || id >= mFallbackEffects.length) {
+ Slog.w(TAG, "Failed to play prebaked effect, no fallback");
+ return 0;
+ }
+ VibrationEffect effect = mFallbackEffects[id];
+ Vibration fallbackVib =
+ new Vibration(vib.mToken, effect, vib.mUsageHint, vib.mUid, vib.mOpPkg);
+ startVibrationInnerLocked(fallbackVib);
+ }
+ return 0;
+ }
+
+ private void noteVibratorOnLocked(int uid, long millis) {
+ try {
+ mBatteryStatsService.noteVibratorOn(uid, millis);
+ mCurVibUid = uid;
+ } catch (RemoteException e) {
+ }
+ }
+
+ private void noteVibratorOffLocked() {
+ if (mCurVibUid >= 0) {
+ try {
+ mBatteryStatsService.noteVibratorOff(mCurVibUid);
+ } catch (RemoteException e) { }
+ mCurVibUid = -1;
+ }
+ }
+
private class VibrateThread extends Thread {
- final Vibration mVibration;
- boolean mDone;
+ private final VibrationEffect.Waveform mWaveform;
+ private final int mUid;
+ private final int mUsageHint;
- VibrateThread(Vibration vib) {
- mVibration = vib;
- mTmpWorkSource.set(vib.mUid);
+ private boolean mForceStop;
+
+ VibrateThread(VibrationEffect.Waveform waveform, int uid, int usageHint) {
+ mWaveform = waveform;
+ mUid = uid;
+ mUsageHint = usageHint;
+ mTmpWorkSource.set(uid);
mWakeLock.setWorkSource(mTmpWorkSource);
- mWakeLock.acquire();
}
- private void delay(long duration) {
+ private long delayLocked(long duration) {
+ long durationRemaining = duration;
if (duration > 0) {
- long bedtime = duration + SystemClock.uptimeMillis();
+ final long bedtime = duration + SystemClock.uptimeMillis();
do {
try {
- this.wait(duration);
+ this.wait(durationRemaining);
}
- catch (InterruptedException e) {
- }
- if (mDone) {
+ catch (InterruptedException e) { }
+ if (mForceStop) {
break;
}
- duration = bedtime - SystemClock.uptimeMillis();
- } while (duration > 0);
+ durationRemaining = bedtime - SystemClock.uptimeMillis();
+ } while (durationRemaining > 0);
+ return duration - durationRemaining;
}
+ return 0;
}
public void run() {
Process.setThreadPriority(Process.THREAD_PRIORITY_URGENT_DISPLAY);
+ mWakeLock.acquire();
+ try {
+ boolean finished = playWaveform();
+ if (finished) {
+ onVibrationFinished();
+ }
+ } finally {
+ mWakeLock.release();
+ }
+ }
+
+ /**
+ * Play the waveform.
+ *
+ * @return true if it finished naturally, false otherwise (e.g. it was canceled).
+ */
+ public boolean playWaveform() {
synchronized (this) {
- final long[] pattern = mVibration.mPattern;
- final int len = pattern.length;
- final int repeat = mVibration.mRepeat;
- final int uid = mVibration.mUid;
- final int usageHint = mVibration.mUsageHint;
- int index = 0;
- long duration = 0;
+ final long[] timings = mWaveform.getTimings();
+ final int[] amplitudes = mWaveform.getAmplitudes();
+ final int len = timings.length;
+ final int repeat = mWaveform.getRepeatIndex();
- while (!mDone) {
- // add off-time duration to any accumulated on-time duration
+ int index = 0;
+ long onDuration = 0;
+ while (!mForceStop) {
if (index < len) {
- duration += pattern[index++];
- }
-
- // sleep until it is time to start the vibrator
- delay(duration);
- if (mDone) {
- break;
- }
+ final int amplitude = amplitudes[index];
+ final long duration = timings[index++];
+ if (duration <= 0) {
+ continue;
+ }
+ if (amplitude != 0) {
+ if (onDuration <= 0) {
+ // Telling the vibrator to start multiple times usually causes
+ // effects to feel "choppy" because the motor resets at every on
+ // command. Instead we figure out how long our next "on" period is
+ // going to be, tell the motor to stay on for the full duration,
+ // and then wake up to change the amplitude at the appropriate
+ // intervals.
+ onDuration =
+ getTotalOnDuration(timings, amplitudes, index - 1, repeat);
+ doVibratorOn(onDuration, amplitude, mUid, mUsageHint);
+ } else {
+ doVibratorSetAmplitude(amplitude);
+ }
+ }
- if (index < len) {
- // read on-time duration and start the vibrator
- // duration is saved for delay() at top of loop
- duration = pattern[index++];
- if (duration > 0) {
- VibratorService.this.doVibratorOn(duration, uid, usageHint);
+ long waitTime = delayLocked(duration);
+ if (amplitude != 0) {
+ onDuration -= waitTime;
}
+ } else if (repeat < 0) {
+ break;
} else {
- if (repeat < 0) {
- break;
- } else {
- index = repeat;
- duration = 0;
- }
+ index = repeat;
}
}
- mWakeLock.release();
+ return !mForceStop;
+ }
+ }
+
+ public void cancel() {
+ synchronized (this) {
+ mThread.mForceStop = true;
+ mThread.notify();
}
- synchronized (mVibrations) {
- if (mThread == this) {
- mThread = null;
+ }
+
+ /**
+ * Get the duration the vibrator will be on starting at startIndex until the next time it's
+ * off.
+ */
+ private long getTotalOnDuration(
+ long[] timings, int[] amplitudes, int startIndex, int repeatIndex) {
+ int i = startIndex;
+ long timing = 0;
+ while(amplitudes[i] != 0) {
+ timing += timings[i++];
+ if (i >= timings.length) {
+ if (repeatIndex >= 0) {
+ i = repeatIndex;
+ } else {
+ break;
+ }
}
- if (!mDone) {
- // If this vibration finished naturally, start the next
- // vibration.
- unlinkVibration(mVibration);
- startNextVibrationLocked();
+ if (i == startIndex) {
+ return 1000;
}
}
+ return timing;
}
}
@@ -752,7 +857,7 @@ public class VibratorService extends IVibratorService.Stub
@Override
public void onReceive(Context context, Intent intent) {
if (intent.getAction().equals(Intent.ACTION_SCREEN_OFF)) {
- synchronized (mVibrations) {
+ synchronized (mLock) {
// When the system is entering a non-interactive state, we want
// to cancel vibrations in case a misbehaving app has abandoned
// them. However it may happen that the system is currently playing
@@ -762,16 +867,6 @@ public class VibratorService extends IVibratorService.Stub
&& !mCurrentVibration.isSystemHapticFeedback()) {
doCancelVibrateLocked();
}
-
- // Clear all remaining vibrations.
- Iterator<Vibration> it = mVibrations.iterator();
- while (it.hasNext()) {
- Vibration vibration = it.next();
- if (vibration != mCurrentVibration) {
- unlinkVibration(vibration);
- it.remove();
- }
- }
}
}
}
@@ -788,7 +883,7 @@ public class VibratorService extends IVibratorService.Stub
return;
}
pw.println("Previous vibrations:");
- synchronized (mVibrations) {
+ synchronized (mLock) {
for (VibrationInfo info : mPreviousVibrations) {
pw.print(" ");
pw.println(info.toString());
@@ -830,7 +925,10 @@ public class VibratorService extends IVibratorService.Stub
if (description == null) {
description = "Shell command";
}
- vibrate(Binder.getCallingUid(), description, duration, AudioAttributes.USAGE_UNKNOWN,
+
+ VibrationEffect effect =
+ VibrationEffect.createOneShot(duration, VibrationEffect.DEFAULT_AMPLITUDE);
+ vibrate(Binder.getCallingUid(), description, effect, AudioAttributes.USAGE_UNKNOWN,
mToken);
return 0;
}
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 3dcc5d997e08..ae5c2c1c240c 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -105,6 +105,7 @@ import android.os.SystemProperties;
import android.os.UserHandle;
import android.os.UserManager;
import android.os.Vibrator;
+import android.os.VibrationEffect;
import android.provider.Settings;
import android.service.notification.Adjustment;
import android.service.notification.Condition;
@@ -3613,9 +3614,12 @@ public class NotificationManagerService extends SystemService {
// notifying app does not have the VIBRATE permission.
long identity = Binder.clearCallingIdentity();
try {
- mVibrator.vibrate(record.sbn.getUid(), record.sbn.getOpPkg(), vibration,
- ((record.getNotification().flags & Notification.FLAG_INSISTENT) != 0)
- ? 0: -1, record.getAudioAttributes());
+ final boolean insistent =
+ (record.getNotification().flags & Notification.FLAG_INSISTENT) != 0;
+ final VibrationEffect effect = VibrationEffect.createWaveform(
+ vibration, insistent ? 0 : -1 /*repeatIndex*/);
+ mVibrator.vibrate(record.sbn.getUid(), record.sbn.getOpPkg(),
+ effect, record.getAudioAttributes());
return true;
} finally{
Binder.restoreCallingIdentity(identity);
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 548fa1ec6d75..9386532450cb 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -177,6 +177,7 @@ import android.os.SystemProperties;
import android.os.UEventObserver;
import android.os.UserHandle;
import android.os.Vibrator;
+import android.os.VibrationEffect;
import android.provider.MediaStore;
import android.provider.Settings;
import android.service.dreams.DreamManagerInternal;
@@ -7524,17 +7525,35 @@ public class PhoneWindowManager implements WindowManagerPolicy {
if (hapticsDisabled && !always) {
return false;
}
- long[] pattern = null;
+
+ VibrationEffect effect = getVibrationEffect(effectId);
+ if (effect == null) {
+ return false;
+ }
+
+ int owningUid;
+ String owningPackage;
+ if (win != null) {
+ owningUid = win.getOwningUid();
+ owningPackage = win.getOwningPackage();
+ } else {
+ owningUid = android.os.Process.myUid();
+ owningPackage = mContext.getOpPackageName();
+ }
+ mVibrator.vibrate(owningUid, owningPackage, effect, VIBRATION_ATTRIBUTES);
+ return true;
+ }
+
+ private VibrationEffect getVibrationEffect(int effectId) {
+ long[] pattern;
switch (effectId) {
+ case HapticFeedbackConstants.VIRTUAL_KEY:
+ return VibrationEffect.get(VibrationEffect.EFFECT_CLICK);
case HapticFeedbackConstants.LONG_PRESS:
pattern = mLongPressVibePattern;
break;
- case HapticFeedbackConstants.VIRTUAL_KEY:
- pattern = mVirtualKeyVibePattern;
- break;
case HapticFeedbackConstants.KEYBOARD_TAP:
- pattern = mKeyboardTapVibePattern;
- break;
+ return VibrationEffect.get(VibrationEffect.EFFECT_CLICK);
case HapticFeedbackConstants.CLOCK_TICK:
pattern = mClockTickVibePattern;
break;
@@ -7551,25 +7570,15 @@ public class PhoneWindowManager implements WindowManagerPolicy {
pattern = mContextClickVibePattern;
break;
default:
- return false;
- }
- int owningUid;
- String owningPackage;
- if (win != null) {
- owningUid = win.getOwningUid();
- owningPackage = win.getOwningPackage();
- } else {
- owningUid = android.os.Process.myUid();
- owningPackage = mContext.getOpPackageName();
+ return null;
}
if (pattern.length == 1) {
// One-shot vibration
- mVibrator.vibrate(owningUid, owningPackage, pattern[0], VIBRATION_ATTRIBUTES);
+ return VibrationEffect.createOneShot(pattern[0], VibrationEffect.DEFAULT_AMPLITUDE);
} else {
// Pattern vibration
- mVibrator.vibrate(owningUid, owningPackage, pattern, -1, VIBRATION_ATTRIBUTES);
+ return VibrationEffect.createWaveform(pattern, -1);
}
- return true;
}
@Override
diff --git a/services/core/jni/com_android_server_VibratorService.cpp b/services/core/jni/com_android_server_VibratorService.cpp
index 50bae7943574..76ce890717d7 100644
--- a/services/core/jni/com_android_server_VibratorService.cpp
+++ b/services/core/jni/com_android_server_VibratorService.cpp
@@ -27,8 +27,12 @@
#include <utils/Log.h>
#include <hardware/vibrator.h>
+#include <inttypes.h>
#include <stdio.h>
+using android::hardware::Return;
+using android::hardware::vibrator::V1_0::Effect;
+using android::hardware::vibrator::V1_0::EffectStrength;
using android::hardware::vibrator::V1_0::IVibrator;
using android::hardware::vibrator::V1_0::Status;
@@ -59,8 +63,8 @@ static void vibratorOn(JNIEnv* /* env */, jobject /* clazz */, jlong timeout_ms)
{
if (mHal != nullptr) {
Status retStatus = mHal->on(timeout_ms);
- if (retStatus == Status::ERR) {
- ALOGE("vibratorOn command failed.");
+ if (retStatus != Status::OK) {
+ ALOGE("vibratorOn command failed (%" PRIu32 ").", static_cast<uint32_t>(retStatus));
}
} else {
ALOGW("Tried to vibrate but there is no vibrator device.");
@@ -71,19 +75,68 @@ static void vibratorOff(JNIEnv* /* env */, jobject /* clazz */)
{
if (mHal != nullptr) {
Status retStatus = mHal->off();
- if (retStatus == Status::ERR) {
- ALOGE("vibratorOff command failed.");
+ if (retStatus != Status::OK) {
+ ALOGE("vibratorOff command failed (%" PRIu32 ").", static_cast<uint32_t>(retStatus));
}
} else {
ALOGW("Tried to stop vibrating but there is no vibrator device.");
}
}
+static jlong vibratorSupportsAmplitudeControl(JNIEnv*, jobject) {
+ if (mHal != nullptr) {
+ return mHal->supportsAmplitudeControl();
+ } else {
+ ALOGW("Unable to get max vibration amplitude, there is no vibrator device.");
+ }
+ return false;
+}
+
+static void vibratorSetAmplitude(JNIEnv*, jobject, jint amplitude) {
+ if (mHal != nullptr) {
+ Status status = mHal->setAmplitude(static_cast<uint32_t>(amplitude));
+ if (status != Status::OK) {
+ ALOGE("Failed to set vibrator amplitude (%" PRIu32 ").",
+ static_cast<uint32_t>(status));
+ }
+ } else {
+ ALOGW("Unable to set vibration amplitude, there is no vibrator device.");
+ }
+}
+
+static jlong vibratorPerformEffect(JNIEnv*, jobject, jlong effect, jint strength) {
+ if (mHal != nullptr) {
+ Status status;
+ uint32_t lengthMs;
+ mHal->perform(static_cast<Effect>(effect), static_cast<EffectStrength>(strength),
+ [&status, &lengthMs](Status retStatus, uint32_t retLengthMs) {
+ status = retStatus;
+ lengthMs = retLengthMs;
+ });
+ if (status == Status::OK) {
+ return lengthMs;
+ } else if (status != Status::UNSUPPORTED_OPERATION) {
+ // Don't warn on UNSUPPORTED_OPERATION, that's a normal even and just means the motor
+ // doesn't have a pre-defined waveform to perform for it, so we should just fall back
+ // to the framework waveforms.
+ ALOGE("Failed to perform haptic effect: effect=%" PRId64 ", strength=%" PRId32
+ ", error=%" PRIu32 ").", static_cast<int64_t>(effect),
+ static_cast<int32_t>(strength), static_cast<uint32_t>(status));
+ }
+ } else {
+ ALOGW("Unable to perform haptic effect, there is no vibrator device.");
+ }
+ return -1;
+}
+
static const JNINativeMethod method_table[] = {
{ "vibratorExists", "()Z", (void*)vibratorExists },
{ "vibratorInit", "()V", (void*)vibratorInit },
{ "vibratorOn", "(J)V", (void*)vibratorOn },
- { "vibratorOff", "()V", (void*)vibratorOff }
+ { "vibratorOff", "()V", (void*)vibratorOff },
+ { "vibratorSupportsAmplitudeControl", "()Z", (void*)vibratorSupportsAmplitudeControl},
+ { "vibratorSetAmplitude", "(I)V", (void*)vibratorSetAmplitude},
+ { "vibratorPerformEffect", "(JJ)J", (void*)vibratorPerformEffect}
};
int register_android_server_VibratorService(JNIEnv *env)
diff --git a/services/tests/notification/src/com/android/server/notification/BuzzBeepBlinkTest.java b/services/tests/notification/src/com/android/server/notification/BuzzBeepBlinkTest.java
index 15f7557c92ef..e28566931d23 100644
--- a/services/tests/notification/src/com/android/server/notification/BuzzBeepBlinkTest.java
+++ b/services/tests/notification/src/com/android/server/notification/BuzzBeepBlinkTest.java
@@ -39,12 +39,14 @@ import android.os.Handler;
import android.os.RemoteException;
import android.os.UserHandle;
import android.os.Vibrator;
+import android.os.VibrationEffect;
import android.provider.Settings;
import android.service.notification.StatusBarNotification;
import android.support.test.InstrumentationRegistry;
import android.support.test.runner.AndroidJUnit4;
import android.test.suitebuilder.annotation.SmallTest;
+import org.mockito.ArgumentMatcher;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
@@ -53,6 +55,7 @@ import static org.mockito.Matchers.anyBoolean;
import static org.mockito.Matchers.anyInt;
import static org.mockito.Matchers.anyObject;
import static org.mockito.Matchers.anyString;
+import static org.mockito.Matchers.argThat;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
@@ -78,6 +81,9 @@ public class BuzzBeepBlinkTest {
private int mPid = 2000;
private android.os.UserHandle mUser = UserHandle.of(ActivityManager.getCurrentUser());
+ private VibrateRepeatMatcher mVibrateOnceMatcher = new VibrateRepeatMatcher(-1);
+ private VibrateRepeatMatcher mVibrateLoopMatcher = new VibrateRepeatMatcher(0);
+
private static final long[] CUSTOM_VIBRATION = new long[] {
300, 400, 300, 400, 300, 400, 300, 400, 300, 400, 300, 400,
300, 400, 300, 400, 300, 400, 300, 400, 300, 400, 300, 400,
@@ -90,7 +96,9 @@ public class BuzzBeepBlinkTest {
private static final int CUSTOM_LIGHT_COLOR = Color.BLACK;
private static final int CUSTOM_LIGHT_ON = 10000;
private static final int CUSTOM_LIGHT_OFF = 10000;
- private static final long[] FALLBACK_VIBRATION = new long[] {100, 100, 100};
+ private static final long[] FALLBACK_VIBRATION_PATTERN = new long[] {100, 100, 100};
+ private static final VibrationEffect FALLBACK_VIBRATION =
+ VibrationEffect.createWaveform(FALLBACK_VIBRATION_PATTERN, -1);
@Before
public void setUp() {
@@ -108,7 +116,7 @@ public class BuzzBeepBlinkTest {
mService.setHandler(mHandler);
mService.setLights(mLight);
mService.setScreenOn(false);
- mService.setFallbackVibrationPattern(FALLBACK_VIBRATION);
+ mService.setFallbackVibrationPattern(FALLBACK_VIBRATION_PATTERN);
}
//
@@ -272,18 +280,18 @@ public class BuzzBeepBlinkTest {
}
private void verifyNeverVibrate() {
- verify(mVibrator, never()).vibrate(anyInt(), anyString(), (long[]) anyObject(),
- anyInt(), (AudioAttributes) anyObject());
+ verify(mVibrator, never()).vibrate(anyInt(), anyString(), (VibrationEffect) anyObject(),
+ (AudioAttributes) anyObject());
}
private void verifyVibrate() {
- verify(mVibrator, times(1)).vibrate(anyInt(), anyString(), (long[]) anyObject(),
- eq(-1), (AudioAttributes) anyObject());
+ verify(mVibrator, times(1)).vibrate(anyInt(), anyString(), argThat(mVibrateOnceMatcher),
+ (AudioAttributes) anyObject());
}
private void verifyVibrateLooped() {
- verify(mVibrator, times(1)).vibrate(anyInt(), anyString(), (long[]) anyObject(),
- eq(0), (AudioAttributes) anyObject());
+ verify(mVibrator, times(1)).vibrate(anyInt(), anyString(), argThat(mVibrateLoopMatcher),
+ (AudioAttributes) anyObject());
}
private void verifyStopVibrate() {
@@ -485,8 +493,10 @@ public class BuzzBeepBlinkTest {
mService.buzzBeepBlinkLocked(r);
- verify(mVibrator, times(1)).vibrate(anyInt(), anyString(), eq(r.getVibration()),
- eq(-1), (AudioAttributes) anyObject());
+ VibrationEffect effect = VibrationEffect.createWaveform(r.getVibration(), -1);
+
+ verify(mVibrator, times(1)).vibrate(anyInt(), anyString(), eq(effect),
+ (AudioAttributes) anyObject());
}
@Test
@@ -501,7 +511,7 @@ public class BuzzBeepBlinkTest {
mService.buzzBeepBlinkLocked(r);
verify(mVibrator, times(1)).vibrate(anyInt(), anyString(), eq(FALLBACK_VIBRATION),
- eq(-1), (AudioAttributes) anyObject());
+ (AudioAttributes) anyObject());
verify(mRingtonePlayer, never()).playAsync
(anyObject(), anyObject(), anyBoolean(), anyObject());
}
@@ -667,4 +677,27 @@ public class BuzzBeepBlinkTest {
mService.buzzBeepBlinkLocked(s);
verifyStopVibrate();
}
+
+ static class VibrateRepeatMatcher implements ArgumentMatcher<VibrationEffect> {
+ private final int mRepeatIndex;
+
+ VibrateRepeatMatcher(int repeatIndex) {
+ mRepeatIndex = repeatIndex;
+ }
+
+ @Override
+ public boolean matches(VibrationEffect actual) {
+ if (actual instanceof VibrationEffect.Waveform &&
+ ((VibrationEffect.Waveform) actual).getRepeatIndex() == mRepeatIndex) {
+ return true;
+ }
+ // All non-waveform effects are essentially one shots.
+ return mRepeatIndex == -1;
+ }
+
+ @Override
+ public String toString() {
+ return "repeatIndex=" + mRepeatIndex;
+ }
+ }
}
diff --git a/tests/permission/src/com/android/framework/permission/tests/VibratorServicePermissionTest.java b/tests/permission/src/com/android/framework/permission/tests/VibratorServicePermissionTest.java
index b12ed94467b7..2757296f588f 100644
--- a/tests/permission/src/com/android/framework/permission/tests/VibratorServicePermissionTest.java
+++ b/tests/permission/src/com/android/framework/permission/tests/VibratorServicePermissionTest.java
@@ -24,6 +24,7 @@ import android.os.IVibratorService;
import android.os.Process;
import android.os.RemoteException;
import android.os.ServiceManager;
+import android.os.VibrationEffect;
import android.test.suitebuilder.annotation.SmallTest;
/**
@@ -48,7 +49,9 @@ public class VibratorServicePermissionTest extends TestCase {
*/
public void testVibrate() throws RemoteException {
try {
- mVibratorService.vibrate(Process.myUid(), null, 2000, AudioManager.STREAM_ALARM,
+ final VibrationEffect effect =
+ VibrationEffect.createOneShot(100, VibrationEffect.DEFAULT_AMPLITUDE);
+ mVibratorService.vibrate(Process.myUid(), null, effect, AudioManager.STREAM_ALARM,
new Binder());
fail("vibrate did not throw SecurityException as expected");
} catch (SecurityException e) {
@@ -57,23 +60,6 @@ public class VibratorServicePermissionTest extends TestCase {
}
/**
- * Test that calling {@link android.os.IVibratorService#vibratePattern(long[],
- * int, android.os.IBinder)} requires permissions.
- * <p>Tests permission:
- * {@link android.Manifest.permission#VIBRATE}
- * @throws RemoteException
- */
- public void testVibratePattern() throws RemoteException {
- try {
- mVibratorService.vibratePattern(Process.myUid(), null, new long[] {0}, 0,
- AudioManager.STREAM_ALARM, new Binder());
- fail("vibratePattern did not throw SecurityException as expected");
- } catch (SecurityException e) {
- // expected
- }
- }
-
- /**
* Test that calling {@link android.os.IVibratorService#cancelVibrate()} requires permissions.
* <p>Tests permission:
* {@link android.Manifest.permission#VIBRATE}