summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--api/current.txt20
-rw-r--r--core/java/android/os/IVibratorService.aidl2
-rw-r--r--core/java/android/os/SystemVibrator.java23
-rw-r--r--core/java/android/os/VibrationEffect.aidl4
-rw-r--r--core/java/android/os/VibrationEffect.java346
-rw-r--r--core/java/android/os/Vibrator.java143
-rw-r--r--services/core/java/com/android/server/VibratorService.java128
-rw-r--r--services/core/jni/com_android_server_VibratorService.cpp67
8 files changed, 675 insertions, 58 deletions
diff --git a/api/current.txt b/api/current.txt
index dda21526b515..c556bd0a4760 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -36927,6 +36927,7 @@ package android.os {
method public static android.os.VibrationEffect createWaveform(long[], int);
method public static android.os.VibrationEffect createWaveform(long[], int[], int);
method public int describeContents();
+ method @NonNull public static android.os.VibrationEffect.Composition startComposition();
field @NonNull public static final android.os.Parcelable.Creator<android.os.VibrationEffect> CREATOR;
field public static final int DEFAULT_AMPLITUDE = -1; // 0xffffffff
field public static final int EFFECT_CLICK = 0; // 0x0
@@ -36935,7 +36936,26 @@ package android.os {
field public static final int EFFECT_TICK = 2; // 0x2
}
+ public static class VibrationEffect.Composition {
+ ctor public VibrationEffect.Composition();
+ method @Nullable public android.os.VibrationEffect.Composition addPrimitive(int);
+ method @Nullable public android.os.VibrationEffect.Composition addPrimitive(int, @FloatRange(from=0.0f, to=1.0f) float);
+ method @Nullable public android.os.VibrationEffect.Composition addPrimitive(int, @FloatRange(from=0.0f, to=1.0f) float, @IntRange(from=0) int);
+ method @NonNull public android.os.VibrationEffect compose();
+ field public static final int PRIMITIVE_CLICK = 1; // 0x1
+ field public static final int PRIMITIVE_LIGHT_TICK = 7; // 0x7
+ field public static final int PRIMITIVE_QUICK_FALL = 6; // 0x6
+ field public static final int PRIMITIVE_QUICK_RISE = 4; // 0x4
+ field public static final int PRIMITIVE_SLOW_RISE = 5; // 0x5
+ field public static final int PRIMITIVE_SPIN = 3; // 0x3
+ field public static final int PRIMITIVE_THUD = 2; // 0x2
+ }
+
public abstract class Vibrator {
+ method @Nullable public Boolean areAllEffectsSupported(@NonNull int...);
+ method public boolean areAllPrimitivesSupported(@NonNull int...);
+ method @Nullable public boolean[] areEffectsSupported(@NonNull int...);
+ method @NonNull public boolean[] arePrimitivesSupported(@NonNull int...);
method @RequiresPermission(android.Manifest.permission.VIBRATE) public abstract void cancel();
method public abstract boolean hasAmplitudeControl();
method public abstract boolean hasVibrator();
diff --git a/core/java/android/os/IVibratorService.aidl b/core/java/android/os/IVibratorService.aidl
index 416d69229536..e201e43b5586 100644
--- a/core/java/android/os/IVibratorService.aidl
+++ b/core/java/android/os/IVibratorService.aidl
@@ -24,6 +24,8 @@ interface IVibratorService
{
boolean hasVibrator();
boolean hasAmplitudeControl();
+ boolean[] areEffectsSupported(in int[] effectIds);
+ boolean[] arePrimitivesSupported(in int[] primitiveIds);
boolean setAlwaysOnEffect(int uid, String opPkg, int alwaysOnId, in VibrationEffect effect,
in VibrationAttributes attributes);
void vibrate(int uid, String opPkg, in VibrationEffect effect,
diff --git a/core/java/android/os/SystemVibrator.java b/core/java/android/os/SystemVibrator.java
index 8050454a8ac3..faf4a36ff577 100644
--- a/core/java/android/os/SystemVibrator.java
+++ b/core/java/android/os/SystemVibrator.java
@@ -16,6 +16,7 @@
package android.os;
+import android.annotation.NonNull;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.media.AudioAttributes;
@@ -104,6 +105,28 @@ public class SystemVibrator extends Vibrator {
}
@Override
+ public boolean[] areEffectsSupported(@VibrationEffect.EffectType int... effectIds) {
+ try {
+ return mService.areEffectsSupported(effectIds);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed to query effect support");
+ }
+ return new boolean[effectIds.length];
+ }
+
+ @Override
+ public boolean[] arePrimitivesSupported(
+ @NonNull @VibrationEffect.Composition.Primitive int... primitiveIds) {
+ try {
+ return mService.arePrimitivesSupported(primitiveIds);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed to query effect support");
+ }
+ return new boolean[primitiveIds.length];
+ }
+
+
+ @Override
public void cancel() {
if (mService == null) {
return;
diff --git a/core/java/android/os/VibrationEffect.aidl b/core/java/android/os/VibrationEffect.aidl
index dcc79d798c3d..89478fac2f1a 100644
--- a/core/java/android/os/VibrationEffect.aidl
+++ b/core/java/android/os/VibrationEffect.aidl
@@ -17,6 +17,4 @@
package android.os;
parcelable VibrationEffect;
-parcelable VibrationEffect.OneShotVibration;
-parcelable VibrationEffect.WaveformVibration;
-parcelable VibrationEffect.EffectVibration;
+parcelable VibrationEffect.Composition.PrimitiveEffect; \ No newline at end of file
diff --git a/core/java/android/os/VibrationEffect.java b/core/java/android/os/VibrationEffect.java
index 75b4724c7d26..2d218f4f2541 100644
--- a/core/java/android/os/VibrationEffect.java
+++ b/core/java/android/os/VibrationEffect.java
@@ -16,7 +16,9 @@
package android.os;
+import android.annotation.FloatRange;
import android.annotation.IntDef;
+import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.TestApi;
@@ -28,9 +30,14 @@ import android.hardware.vibrator.V1_3.Effect;
import android.net.Uri;
import android.util.MathUtils;
+import com.android.internal.util.Preconditions;
+
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
import java.util.Arrays;
+import java.util.List;
+import java.util.Objects;
/**
* A VibrationEffect describes a haptic effect to be performed by a {@link Vibrator}.
@@ -41,6 +48,8 @@ public abstract class VibrationEffect implements Parcelable {
private static final int PARCEL_TOKEN_ONE_SHOT = 1;
private static final int PARCEL_TOKEN_WAVEFORM = 2;
private static final int PARCEL_TOKEN_EFFECT = 3;
+ private static final int PARCEL_TOKEN_COMPOSITION = 4;
+
/**
* The default vibration strength of the device.
@@ -359,6 +368,16 @@ public abstract class VibrationEffect implements Parcelable {
return null;
}
+ /**
+ * Start composing a haptic effect.
+ *
+ * @see VibrationEffect.Composition
+ */
+ @NonNull
+ public static VibrationEffect.Composition startComposition() {
+ return new VibrationEffect.Composition();
+ }
+
@Override
public int describeContents() {
return 0;
@@ -839,6 +858,331 @@ public abstract class VibrationEffect implements Parcelable {
};
}
+ /** @hide */
+ public static final class Composed extends VibrationEffect implements Parcelable {
+ private final ArrayList<Composition.PrimitiveEffect> mPrimitiveEffects;
+
+ /**
+ * @hide
+ */
+ @SuppressWarnings("unchecked")
+ public Composed(@NonNull Parcel in) {
+ this(in.readArrayList(Composed.class.getClassLoader()));
+ }
+
+ /**
+ * @hide
+ */
+ public Composed(List<Composition.PrimitiveEffect> effects) {
+ mPrimitiveEffects = new ArrayList<>(Objects.requireNonNull(effects));
+ }
+
+ /**
+ * @hide
+ */
+ @NonNull
+ public List<Composition.PrimitiveEffect> getPrimitiveEffects() {
+ return mPrimitiveEffects;
+ }
+
+ @Override
+ public long getDuration() {
+ return -1;
+ }
+
+
+ /**
+ * @hide
+ */
+ @Override
+ public void validate() {
+ for (Composition.PrimitiveEffect effect : mPrimitiveEffects) {
+ Composition.checkPrimitive(effect.id);
+ Preconditions.checkArgumentInRange(
+ effect.scale, 0.0f, 1.0f, "scale");
+ }
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel out, int flags) {
+ out.writeInt(PARCEL_TOKEN_COMPOSITION);
+ out.writeList(mPrimitiveEffects);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ Composed composed = (Composed) o;
+ return mPrimitiveEffects.equals(composed.mPrimitiveEffects);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mPrimitiveEffects);
+ }
+
+ @Override
+ public String toString() {
+ return "Composed{mPrimitiveEffects=" + mPrimitiveEffects + '}';
+ }
+
+ public static final @NonNull Parcelable.Creator<Composed> CREATOR =
+ new Parcelable.Creator<Composed>() {
+ @Override
+ public Composed createFromParcel(@NonNull Parcel in) {
+ // Skip the type token
+ in.readInt();
+ return new Composed(in);
+ }
+
+ @Override
+ @NonNull
+ public Composed[] newArray(int size) {
+ return new Composed[size];
+ }
+ };
+ }
+
+ /**
+ * A composition of haptic primitives that, when combined, create a single haptic effect.
+ *
+ * @see VibrationEffect#startComposition()
+ */
+ public static class Composition {
+ /** @hide */
+ @IntDef(prefix = { "PRIMITIVE_" }, value = {
+ PRIMITIVE_CLICK,
+ PRIMITIVE_THUD,
+ PRIMITIVE_SPIN,
+ PRIMITIVE_QUICK_RISE,
+ PRIMITIVE_SLOW_RISE,
+ PRIMITIVE_QUICK_FALL,
+ PRIMITIVE_LIGHT_TICK,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface Primitive {}
+
+ /**
+ * No haptic effect. Used to generate extended delays between primitives.
+ * @hide
+ */
+ public static final int PRIMITIVE_NOOP = 0;
+ /**
+ * This effect should produce a sharp, crisp click sensation.
+ */
+ public static final int PRIMITIVE_CLICK = 1;
+ /**
+ * A haptic effect that simulates downwards movement with gravity. Often
+ * followed by extra energy of hitting and reverberation to augment
+ * physicality.
+ */
+ public static final int PRIMITIVE_THUD = 2;
+ /**
+ * A haptic effect that simulates spinning momentum.
+ */
+ public static final int PRIMITIVE_SPIN = 3;
+ /**
+ * A haptic effect that simulates quick upward movement against gravity.
+ */
+ public static final int PRIMITIVE_QUICK_RISE = 4;
+ /**
+ * A haptic effect that simulates slow upward movement against gravity.
+ */
+ public static final int PRIMITIVE_SLOW_RISE = 5;
+ /**
+ * A haptic effect that simulates quick downwards movement with gravity.
+ */
+ public static final int PRIMITIVE_QUICK_FALL = 6;
+ /**
+ * This very short effect should produce a light crisp sensation intended
+ * to be used repetitively for dynamic feedback.
+ */
+ public static final int PRIMITIVE_LIGHT_TICK = 7;
+
+
+ private ArrayList<PrimitiveEffect> mEffects = new ArrayList<>();
+
+ /**
+ * Add a haptic primitive to the end of the current composition.
+ *
+ * Similar to {@link #addPrimitive(int, float, int)}, but with no delay and a
+ * default scale applied.
+ *
+ * @param primitiveId The primitive to add
+ *
+ * @return The {@link Composition} object to enable adding multiple primitives in one chain.
+ */
+ @Nullable
+ public Composition addPrimitive(@Primitive int primitiveId) {
+ addPrimitive(primitiveId, /*scale*/ 1.0f, /*delay*/ 0);
+ return this;
+ }
+
+ /**
+ * Add a haptic primitive to the end of the current composition.
+ *
+ * Similar to {@link #addPrimitive(int, float, int)}, but with no delay.
+ *
+ * @param primitiveId The primitive to add
+ * @param scale The scale to apply to the intensity of the primitive.
+ *
+ * @return The {@link Composition} object to enable adding multiple primitives in one chain.
+ */
+ @Nullable
+ public Composition addPrimitive(@Primitive int primitiveId,
+ @FloatRange(from = 0f, to = 1f) float scale) {
+ addPrimitive(primitiveId, scale, /*delay*/ 0);
+ return this;
+ }
+
+ /**
+ * Add a haptic primitive to the end of the current composition.
+ *
+ * @param primitiveId The primitive to add
+ * @param scale The scale to apply to the intensity of the primitive.
+ * @param delay The amount of time, in milliseconds, to wait before playing the prior
+ * primitive and this one
+ * @return The {@link Composition} object to enable adding multiple primitives in one chain.
+ */
+ @Nullable
+ public Composition addPrimitive(@Primitive int primitiveId,
+ @FloatRange(from = 0f, to = 1f) float scale, @IntRange(from = 0) int delay) {
+ mEffects.add(new PrimitiveEffect(checkPrimitive(primitiveId), scale, delay));
+ return this;
+ }
+
+ /**
+ * Compose all of the added primitives together into a single {@link VibrationEffect}.
+ *
+ * The {@link Composition} object is still valid after this call, so you can continue adding
+ * more primitives to it and generating more {@link VibrationEffect}s by calling this method
+ * again.
+ *
+ * @return The {@link VibrationEffect} resulting from the composition of the primitives.
+ */
+ @NonNull
+ public VibrationEffect compose() {
+ if (mEffects.isEmpty()) {
+ throw new IllegalStateException(
+ "Composition must have at least one element to compose.");
+ }
+ return new VibrationEffect.Composed(mEffects);
+ }
+
+ /**
+ * @throws IllegalArgumentException throws if the primitive ID is not within the valid range
+ * @hide
+ *
+ */
+ static int checkPrimitive(int primitiveId) {
+ Preconditions.checkArgumentInRange(primitiveId, PRIMITIVE_NOOP, PRIMITIVE_LIGHT_TICK,
+ "primitiveId");
+ return primitiveId;
+ }
+
+ /**
+ * Convert the primitive ID to a human readable string for debugging
+ * @param id The ID to convert
+ * @return The ID in a human readable format.
+ * @hide
+ */
+ public static String primitiveToString(@Primitive int id) {
+ switch (id) {
+ case PRIMITIVE_NOOP:
+ return "PRIMITIVE_NOOP";
+ case PRIMITIVE_CLICK:
+ return "PRIMITIVE_CLICK";
+ case PRIMITIVE_THUD:
+ return "PRIMITIVE_THUD";
+ case PRIMITIVE_SPIN:
+ return "PRIMITIVE_SPIN";
+ case PRIMITIVE_QUICK_RISE:
+ return "PRIMITIVE_QUICK_RISE";
+ case PRIMITIVE_SLOW_RISE:
+ return "PRIMITIVE_SLOW_RISE";
+ case PRIMITIVE_QUICK_FALL:
+ return "PRIMITIVE_QUICK_FALL";
+ case PRIMITIVE_LIGHT_TICK:
+ return "PRIMITIVE_LIGHT_TICK";
+
+ default:
+ return Integer.toString(id);
+
+ }
+ }
+
+
+ /**
+ * @hide
+ */
+ public static class PrimitiveEffect implements Parcelable {
+ public int id;
+ public float scale;
+ public int delay;
+
+ PrimitiveEffect(int id, float scale, int delay) {
+ this.id = id;
+ this.scale = scale;
+ this.delay = delay;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(id);
+ dest.writeFloat(scale);
+ dest.writeInt(delay);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public String toString() {
+ return "PrimitiveEffect{"
+ + "id=" + primitiveToString(id)
+ + ", scale=" + scale
+ + ", delay=" + delay
+ + '}';
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ PrimitiveEffect that = (PrimitiveEffect) o;
+ return id == that.id
+ && Float.compare(that.scale, scale) == 0
+ && delay == that.delay;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(id, scale, delay);
+ }
+
+
+ public static final @NonNull Parcelable.Creator<PrimitiveEffect> CREATOR =
+ new Parcelable.Creator<PrimitiveEffect>() {
+ @Override
+ public PrimitiveEffect createFromParcel(Parcel in) {
+ return new PrimitiveEffect(in.readInt(), in.readFloat(), in.readInt());
+ }
+ @Override
+ public PrimitiveEffect[] newArray(int size) {
+ return new PrimitiveEffect[size];
+ }
+ };
+ }
+ }
+
public static final @NonNull Parcelable.Creator<VibrationEffect> CREATOR =
new Parcelable.Creator<VibrationEffect>() {
@Override
@@ -850,6 +1194,8 @@ public abstract class VibrationEffect implements Parcelable {
return new Waveform(in);
} else if (token == PARCEL_TOKEN_EFFECT) {
return new Prebaked(in);
+ } else if (token == PARCEL_TOKEN_COMPOSITION) {
+ return new Composed(in);
} else {
throw new IllegalStateException(
"Unexpected vibration event type token in parcel.");
diff --git a/core/java/android/os/Vibrator.java b/core/java/android/os/Vibrator.java
index ae75f3d0d7e6..f055c60e6a41 100644
--- a/core/java/android/os/Vibrator.java
+++ b/core/java/android/os/Vibrator.java
@@ -17,6 +17,7 @@
package android.os;
import android.annotation.IntDef;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.annotation.SystemService;
@@ -41,37 +42,42 @@ public abstract class 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
+ @IntDef(prefix = {"VIBRATION_INTENSITY_"}, value = {
+ VIBRATION_INTENSITY_OFF,
+ VIBRATION_INTENSITY_LOW,
+ VIBRATION_INTENSITY_MEDIUM,
+ VIBRATION_INTENSITY_HIGH
})
- public @interface VibrationIntensity{}
+ public @interface VibrationIntensity {
+ }
private final String mPackageName;
// The default vibration intensity level for haptic feedback.
@@ -117,6 +123,7 @@ public abstract class Vibrator {
/**
* Get the default vibration intensity for haptic feedback.
+ *
* @hide
*/
public int getDefaultHapticFeedbackIntensity() {
@@ -125,13 +132,16 @@ public abstract class Vibrator {
/**
* Get the default vibration intensity for notifications.
+ *
* @hide
*/
public int getDefaultNotificationVibrationIntensity() {
return mDefaultNotificationVibrationIntensity;
}
- /** Get the default vibration intensity for ringtones.
+ /**
+ * Get the default vibration intensity for ringtones.
+ *
* @hide
*/
public int getDefaultRingVibrationIntensity() {
@@ -156,11 +166,12 @@ public abstract class Vibrator {
* Configure an always-on haptics effect.
*
* @param alwaysOnId The board-specific always-on ID to configure.
- * @param effect Vibration effect to assign to always-on id. Passing null will disable it.
+ * @param effect Vibration effect to assign to always-on id. Passing null will disable it.
* @param attributes {@link AudioAttributes} corresponding to the vibration. For example,
- * specify {@link AudioAttributes#USAGE_ALARM} for alarm vibrations or
- * {@link AudioAttributes#USAGE_NOTIFICATION_RINGTONE} for
- * vibrations associated with incoming calls. May only be null when effect is null.
+ * specify {@link AudioAttributes#USAGE_ALARM} for alarm vibrations or
+ * {@link AudioAttributes#USAGE_NOTIFICATION_RINGTONE} for
+ * vibrations associated with incoming calls. May only be null when effect is
+ * null.
* @hide
*/
@RequiresPermission(android.Manifest.permission.VIBRATE_ALWAYS_ON)
@@ -183,7 +194,6 @@ public abstract class Vibrator {
* Vibrate constantly for the specified period of time.
*
* @param milliseconds The number of milliseconds to vibrate.
- *
* @deprecated Use {@link #vibrate(VibrationEffect)} instead.
*/
@Deprecated
@@ -196,11 +206,10 @@ public abstract class Vibrator {
* Vibrate constantly for the specified period of time.
*
* @param milliseconds The number of milliseconds to vibrate.
- * @param attributes {@link AudioAttributes} corresponding to the vibration. For example,
- * specify {@link AudioAttributes#USAGE_ALARM} for alarm vibrations or
- * {@link AudioAttributes#USAGE_NOTIFICATION_RINGTONE} for
- * vibrations associated with incoming calls.
- *
+ * @param attributes {@link AudioAttributes} corresponding to the vibration. For example,
+ * 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
@@ -231,9 +240,8 @@ public abstract class Vibrator {
* </p>
*
* @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.
- *
+ * @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
@@ -256,14 +264,13 @@ public abstract class Vibrator {
* to start the repeat, or -1 to disable repeating.
* </p>
*
- * @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.
+ * @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.
* @param attributes {@link AudioAttributes} corresponding to the vibration. For example,
- * specify {@link AudioAttributes#USAGE_ALARM} for alarm vibrations or
- * {@link AudioAttributes#USAGE_NOTIFICATION_RINGTONE} for
- * vibrations associated with incoming calls.
- *
+ * 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
@@ -295,8 +302,9 @@ public abstract class Vibrator {
}
/**
- * Like {@link #vibrate(int, String, VibrationEffect, AudioAttributes)}, but allows the
+ * Like {@link #vibrate(VibrationEffect, AudioAttributes)}, but allows the
* caller to specify the vibration is owned by someone else and set reason for vibration.
+ *
* @hide
*/
@RequiresPermission(android.Manifest.permission.VIBRATE)
@@ -304,6 +312,85 @@ public abstract class Vibrator {
String reason, AudioAttributes attributes);
/**
+ * Query whether the vibrator supports the given effects.
+ *
+ * If the returned array is {@code null}, the hardware doesn't support querying its supported
+ * effects. It may support any or all effects, but there's no way to programmatically know
+ * whether a {@link #vibrate} call will be successful.
+ *
+ * If the returned array is non-null, then it will be the same length as the query array and
+ * the value at a given index will contain whether the effect at that same index in the
+ * querying array is supported or not.
+ *
+ * @param effectIds Which effects to query for.
+ * @return Whether the effects are supported. Null when the hardware doesn't tell us what it
+ * supports.
+ */
+ @Nullable
+ public boolean[] areEffectsSupported(
+ @NonNull @VibrationEffect.EffectType int... effectIds) {
+ return new boolean[effectIds.length];
+ }
+
+ /**
+ * Query whether the vibrator supports all of the given effects.
+ *
+ * If the result is {@code null}, the hardware doesn't support querying its supported
+ * effects. It may support any or all effects, but there's no way to programmatically know
+ * whether a {@link #vibrate} call will be successful.
+ *
+ * If the returned array is non-null, then it will return whether all of the effects are
+ * supported by the hardware.
+ *
+ * @param effectIds Which effects to query for.
+ * @return Whether the effects are supported. {@code null} when the hardware doesn't tell us
+ * what it supports.
+ */
+ @Nullable
+ public Boolean areAllEffectsSupported(
+ @NonNull @VibrationEffect.EffectType int... effectIds) {
+ for (boolean supported : areEffectsSupported(effectIds)) {
+ if (!supported) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+
+ /**
+ * Query whether the vibrator supports the given primitives.
+ *
+ * The returned array will be the same length as the query array and the value at a given index
+ * will contain whether the effect at that same index in the querying array is supported or
+ * not.
+ *
+ * @param primitiveIds Which primitives to query for.
+ * @return Whether the primitives are supported.
+ */
+ @NonNull
+ public boolean[] arePrimitivesSupported(
+ @NonNull @VibrationEffect.Composition.Primitive int... primitiveIds) {
+ return new boolean[primitiveIds.length];
+ }
+
+ /**
+ * Query whether the vibrator supports all of the given primitives.
+ *
+ * @param primitiveIds Which primitives to query for.
+ * @return Whether primitives effects are supported.
+ */
+ public boolean areAllPrimitivesSupported(
+ @NonNull @VibrationEffect.Composition.Primitive int... primitiveIds) {
+ for (boolean supported : arePrimitivesSupported(primitiveIds)) {
+ if (!supported) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
* Turn the vibrator off.
*/
@RequiresPermission(android.Manifest.permission.VIBRATE)
diff --git a/services/core/java/com/android/server/VibratorService.java b/services/core/java/com/android/server/VibratorService.java
index 5a56a9fa5367..6bc090a4f7ba 100644
--- a/services/core/java/com/android/server/VibratorService.java
+++ b/services/core/java/com/android/server/VibratorService.java
@@ -16,6 +16,8 @@
package com.android.server;
+import static android.os.VibrationEffect.Composition.PrimitiveEffect;
+
import android.annotation.Nullable;
import android.app.ActivityManager;
import android.app.AppOpsManager;
@@ -73,8 +75,10 @@ import com.android.internal.util.DumpUtils;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Date;
import java.util.LinkedList;
+import java.util.List;
public class VibratorService extends IVibratorService.Stub
implements InputManager.InputDeviceListener {
@@ -128,10 +132,11 @@ public class VibratorService extends IVibratorService.Stub
private final boolean mAllowPriorityVibrationsInLowPowerMode;
private final boolean mSupportsAmplitudeControl;
private final boolean mSupportsExternalControl;
+ private final List<Integer> mSupportedEffects;
private final long mCapabilities;
private final int mDefaultVibrationAmplitude;
private final SparseArray<VibrationEffect> mFallbackEffects;
- private final SparseArray<Integer> mProcStatesCache = new SparseArray();
+ private final SparseArray<Integer> mProcStatesCache = new SparseArray<>();
private final WorkSource mTmpWorkSource = new WorkSource();
private final Handler mH = new Handler();
private final Object mLock = new Object();
@@ -150,7 +155,7 @@ public class VibratorService extends IVibratorService.Stub
// mInputDeviceVibrators lock should be acquired after mLock, if both are
// to be acquired
- private final ArrayList<Vibrator> mInputDeviceVibrators = new ArrayList<Vibrator>();
+ private final ArrayList<Vibrator> mInputDeviceVibrators = new ArrayList<>();
private boolean mVibrateInputDevicesSetting; // guarded by mInputDeviceVibrators
private boolean mInputDeviceListenerRegistered; // guarded by mInputDeviceVibrators
@@ -171,7 +176,10 @@ public class VibratorService extends IVibratorService.Stub
static native void vibratorOff();
static native boolean vibratorSupportsAmplitudeControl();
static native void vibratorSetAmplitude(int amplitude);
+ static native int[] vibratorGetSupportedEffects();
static native long vibratorPerformEffect(long effect, long strength, Vibration vibration);
+ static native void vibratorPerformComposedEffect(
+ VibrationEffect.Composition.PrimitiveEffect[] effect, Vibration vibration);
static native boolean vibratorSupportsExternalControl();
static native void vibratorSetExternalControl(boolean enabled);
static native long vibratorGetCapabilities();
@@ -238,6 +246,8 @@ public class VibratorService extends IVibratorService.Stub
}
}
+ // Called by native
+ @SuppressWarnings("unused")
private void onComplete() {
synchronized (mLock) {
if (this == mCurrentVibration) {
@@ -346,10 +356,11 @@ public class VibratorService extends IVibratorService.Stub
mSupportsAmplitudeControl = vibratorSupportsAmplitudeControl();
mSupportsExternalControl = vibratorSupportsExternalControl();
+ mSupportedEffects = asList(vibratorGetSupportedEffects());
mCapabilities = vibratorGetCapabilities();
mContext = context;
- PowerManager pm = (PowerManager)context.getSystemService(Context.POWER_SERVICE);
+ PowerManager pm = context.getSystemService(PowerManager.class);
mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "*vibrator*");
mWakeLock.setReferenceCounted(true);
@@ -510,12 +521,47 @@ public class VibratorService extends IVibratorService.Stub
}
@Override // Binder call
+ public boolean[] areEffectsSupported(int[] effectIds) {
+ // Return null to indicate that the HAL doesn't actually tell us what effects are
+ // supported.
+ if (mSupportedEffects == null) {
+ return null;
+ }
+ boolean[] supported = new boolean[effectIds.length];
+ for (int i = 0; i < effectIds.length; i++) {
+ supported[i] = mSupportedEffects.contains(effectIds[i]);
+ }
+ return supported;
+ }
+
+ @Override // Binder call
+ public boolean[] arePrimitivesSupported(int[] primitiveIds) {
+ boolean[] supported = new boolean[primitiveIds.length];
+ if (hasCapability(IVibrator.CAP_COMPOSE_EFFECTS)) {
+ Arrays.fill(supported, true);
+ }
+ return supported;
+ }
+
+
+ private static List<Integer> asList(int... vals) {
+ if (vals == null) {
+ return null;
+ }
+ List<Integer> l = new ArrayList<>(vals.length);
+ for (int val : vals) {
+ l.add(val);
+ }
+ return l;
+ }
+
+ @Override // Binder call
public boolean setAlwaysOnEffect(int uid, String opPkg, int alwaysOnId, VibrationEffect effect,
VibrationAttributes attrs) {
if (!hasPermission(android.Manifest.permission.VIBRATE_ALWAYS_ON)) {
throw new SecurityException("Requires VIBRATE_ALWAYS_ON permission");
}
- if ((mCapabilities & IVibrator.CAP_ALWAYS_ON_CONTROL) == 0) {
+ if (!hasCapability(IVibrator.CAP_ALWAYS_ON_CONTROL)) {
Slog.e(TAG, "Always-on effects not supported.");
return false;
}
@@ -817,6 +863,14 @@ public class VibratorService extends IVibratorService.Stub
if (timeout > 0) {
mH.postDelayed(mVibrationEndRunnable, timeout);
}
+ } else if (vib.effect instanceof VibrationEffect.Composed) {
+ Trace.asyncTraceBegin(Trace.TRACE_TAG_VIBRATOR, "vibration", 0);
+ doVibratorComposedEffectLocked(vib);
+ // FIXME: We rely on the completion callback here, but I don't think we require that
+ // devices which support composition also support the completion callback. If we
+ // ever get a device that supports the former but not the latter, then we have no
+ // real way of knowing how long a given effect should last.
+ mH.postDelayed(mVibrationEndRunnable, 10000);
} else {
Slog.e(TAG, "Unknown vibration type, ignoring");
}
@@ -1148,7 +1202,7 @@ public class VibratorService extends IVibratorService.Stub
}
} 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 when enabled, so we always have to enable first, then set the
// amplitude.
vibratorOn(millis);
doVibratorSetAmplitude(amplitude);
@@ -1201,7 +1255,7 @@ public class VibratorService extends IVibratorService.Stub
long duration = vibratorPerformEffect(prebaked.getId(),
prebaked.getEffectStrength(), vib);
long timeout = duration;
- if ((mCapabilities & IVibrator.CAP_PERFORM_CALLBACK) != 0) {
+ if (hasCapability(IVibrator.CAP_PERFORM_CALLBACK)) {
timeout *= ASYNC_TIMEOUT_MULTIPLIER;
}
if (timeout > 0) {
@@ -1229,6 +1283,41 @@ public class VibratorService extends IVibratorService.Stub
}
}
+ @GuardedBy("mLock")
+ private void doVibratorComposedEffectLocked(Vibration vib) {
+ Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "doVibratorComposedEffectLocked");
+
+ try {
+ final VibrationEffect.Composed composed = (VibrationEffect.Composed) vib.effect;
+ final boolean usingInputDeviceVibrators;
+ synchronized (mInputDeviceVibrators) {
+ usingInputDeviceVibrators = !mInputDeviceVibrators.isEmpty();
+ }
+ // Input devices don't support composed effect, so skip trying it with them.
+ if (usingInputDeviceVibrators) {
+ return;
+ }
+
+ if (!hasCapability(IVibrator.CAP_COMPOSE_EFFECTS)) {
+ return;
+ }
+
+ PrimitiveEffect[] primitiveEffects =
+ composed.getPrimitiveEffects().toArray(new PrimitiveEffect[0]);
+ vibratorPerformComposedEffect(primitiveEffects, vib);
+
+ // Composed effects don't actually give us an estimated duration, so we just guess here.
+ noteVibratorOnLocked(vib.uid, 10 * primitiveEffects.length);
+ } finally {
+ Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
+ }
+
+ }
+
+ private boolean hasCapability(long capability) {
+ return (mCapabilities & capability) == capability;
+ }
+
private VibrationEffect getFallbackEffect(int effectId) {
return mFallbackEffects.get(effectId);
}
@@ -1424,7 +1513,7 @@ public class VibratorService extends IVibratorService.Stub
long[] timings, int[] amplitudes, int startIndex, int repeatIndex) {
int i = startIndex;
long timing = 0;
- while(amplitudes[i] != 0) {
+ while (amplitudes[i] != 0) {
timing += timings[i++];
if (i >= timings.length) {
if (repeatIndex >= 0) {
@@ -1475,18 +1564,14 @@ public class VibratorService extends IVibratorService.Stub
} else {
pw.println("null");
}
- pw.print(" mCurrentExternalVibration=");
- if (mCurrentExternalVibration != null) {
- pw.println(mCurrentExternalVibration.toString());
- } else {
- pw.println("null");
- }
+ pw.print(" mCurrentExternalVibration=" + mCurrentExternalVibration);
pw.println(" mVibratorUnderExternalControl=" + mVibratorUnderExternalControl);
pw.println(" mLowPowerMode=" + mLowPowerMode);
pw.println(" mHapticFeedbackIntensity=" + mHapticFeedbackIntensity);
pw.println(" mNotificationIntensity=" + mNotificationIntensity);
pw.println(" mRingIntensity=" + mRingIntensity);
- pw.println("");
+ pw.println(" mSupportedEffects=" + mSupportedEffects);
+ pw.println();
pw.println(" Previous ring vibrations:");
for (VibrationInfo info : mPreviousRingVibrations) {
pw.print(" ");
@@ -1495,34 +1580,29 @@ public class VibratorService extends IVibratorService.Stub
pw.println(" Previous notification vibrations:");
for (VibrationInfo info : mPreviousNotificationVibrations) {
- pw.print(" ");
- pw.println(info.toString());
+ pw.println(" " + info);
}
pw.println(" Previous alarm vibrations:");
for (VibrationInfo info : mPreviousAlarmVibrations) {
- pw.print(" ");
- pw.println(info.toString());
+ pw.println(" " + info);
}
pw.println(" Previous vibrations:");
for (VibrationInfo info : mPreviousVibrations) {
- pw.print(" ");
- pw.println(info.toString());
+ pw.println(" " + info);
}
pw.println(" Previous external vibrations:");
for (ExternalVibration vib : mPreviousExternalVibrations) {
- pw.print(" ");
- pw.println(vib.toString());
+ pw.println(" " + vib);
}
}
}
@Override
public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err,
- String[] args, ShellCallback callback, ResultReceiver resultReceiver)
- throws RemoteException {
+ String[] args, ShellCallback callback, ResultReceiver resultReceiver) {
new VibratorShellCommand(this).exec(this, in, out, err, args, callback, resultReceiver);
}
diff --git a/services/core/jni/com_android_server_VibratorService.cpp b/services/core/jni/com_android_server_VibratorService.cpp
index 6811e6d0e6f2..5a8e25e4cf1c 100644
--- a/services/core/jni/com_android_server_VibratorService.cpp
+++ b/services/core/jni/com_android_server_VibratorService.cpp
@@ -49,6 +49,12 @@ namespace android {
static jmethodID sMethodIdOnComplete;
+static struct {
+ jfieldID id;
+ jfieldID scale;
+ jfieldID delay;
+} gPrimitiveClassInfo;
+
static_assert(static_cast<uint8_t>(V1_0::EffectStrength::LIGHT) ==
static_cast<uint8_t>(aidl::EffectStrength::LIGHT));
static_assert(static_cast<uint8_t>(V1_0::EffectStrength::MEDIUM) ==
@@ -333,6 +339,21 @@ static void vibratorSetExternalControl(JNIEnv*, jclass, jboolean enabled) {
}
}
+static jintArray vibratorGetSupportedEffects(JNIEnv *env, jclass) {
+ if (auto hal = getHal<aidl::IVibrator>()) {
+ std::vector<aidl::Effect> supportedEffects;
+ if (!hal->call(&aidl::IVibrator::getSupportedEffects, &supportedEffects).isOk()) {
+ return nullptr;
+ }
+ jintArray arr = env->NewIntArray(supportedEffects.size());
+ env->SetIntArrayRegion(arr, 0, supportedEffects.size(),
+ reinterpret_cast<jint*>(supportedEffects.data()));
+ return arr;
+ } else {
+ return nullptr;
+ }
+}
+
static jlong vibratorPerformEffect(JNIEnv* env, jclass, jlong effect, jlong strength,
jobject vibration) {
if (auto hal = getHal<aidl::IVibrator>()) {
@@ -398,6 +419,37 @@ static jlong vibratorPerformEffect(JNIEnv* env, jclass, jlong effect, jlong stre
return -1;
}
+static aidl::CompositeEffect effectFromJavaPrimitive(JNIEnv* env, jobject primitive) {
+ aidl::CompositeEffect effect;
+ effect.primitive = static_cast<aidl::CompositePrimitive>(
+ env->GetIntField(primitive, gPrimitiveClassInfo.id));
+ effect.scale = static_cast<float>(env->GetFloatField(primitive, gPrimitiveClassInfo.scale));
+ effect.delayMs = static_cast<int>(env->GetIntField(primitive, gPrimitiveClassInfo.delay));
+ return effect;
+}
+
+static void vibratorPerformComposedEffect(JNIEnv* env, jclass, jobjectArray composition,
+ jobject vibration) {
+ auto hal = getHal<aidl::IVibrator>();
+ if (!hal) {
+ return;
+ }
+ size_t size = env->GetArrayLength(composition);
+ std::vector<aidl::CompositeEffect> effects;
+ for (size_t i = 0; i < size; i++) {
+ jobject element = env->GetObjectArrayElement(composition, i);
+ effects.push_back(effectFromJavaPrimitive(env, element));
+ }
+ sp<AidlVibratorCallback> effectCallback = new AidlVibratorCallback(env, vibration);
+
+ auto status = hal->call(&aidl::IVibrator::compose, effects, effectCallback);
+ if (!status.isOk()) {
+ if (status.exceptionCode() != binder::Status::EX_UNSUPPORTED_OPERATION) {
+ ALOGE("Failed to play haptic effect composition");
+ }
+ }
+}
+
static jlong vibratorGetCapabilities(JNIEnv*, jclass) {
if (auto hal = getHal<aidl::IVibrator>()) {
int32_t cap = 0;
@@ -433,7 +485,12 @@ static const JNINativeMethod method_table[] = {
{ "vibratorSupportsAmplitudeControl", "()Z", (void*)vibratorSupportsAmplitudeControl},
{ "vibratorSetAmplitude", "(I)V", (void*)vibratorSetAmplitude},
{ "vibratorPerformEffect", "(JJLcom/android/server/VibratorService$Vibration;)J",
- (void*)vibratorPerformEffect},
+ (void*)vibratorPerformEffect},
+ { "vibratorPerformComposedEffect",
+ "([Landroid/os/VibrationEffect$Composition$PrimitiveEffect;Lcom/android/server/VibratorService$Vibration;)V",
+ (void*)vibratorPerformComposedEffect},
+ { "vibratorGetSupportedEffects", "()[I",
+ (void*)vibratorGetSupportedEffects},
{ "vibratorSupportsExternalControl", "()Z", (void*)vibratorSupportsExternalControl},
{ "vibratorSetExternalControl", "(Z)V", (void*)vibratorSetExternalControl},
{ "vibratorGetCapabilities", "()J", (void*)vibratorGetCapabilities},
@@ -441,11 +498,15 @@ static const JNINativeMethod method_table[] = {
{ "vibratorAlwaysOnDisable", "(J)V", (void*)vibratorAlwaysOnDisable},
};
-int register_android_server_VibratorService(JNIEnv *env)
-{
+int register_android_server_VibratorService(JNIEnv *env) {
sMethodIdOnComplete = GetMethodIDOrDie(env,
FindClassOrDie(env, "com/android/server/VibratorService$Vibration"),
"onComplete", "()V");
+ jclass primitiveClass = FindClassOrDie(env,
+ "android/os/VibrationEffect$Composition$PrimitiveEffect");
+ gPrimitiveClassInfo.id = GetFieldIDOrDie(env, primitiveClass, "id", "I");
+ gPrimitiveClassInfo.scale = GetFieldIDOrDie(env, primitiveClass, "scale", "F");
+ gPrimitiveClassInfo.delay = GetFieldIDOrDie(env, primitiveClass, "delay", "I");
return jniRegisterNativeMethods(env, "com/android/server/VibratorService",
method_table, NELEM(method_table));
}