diff options
| -rw-r--r-- | api/current.txt | 20 | ||||
| -rw-r--r-- | core/java/android/os/IVibratorService.aidl | 2 | ||||
| -rw-r--r-- | core/java/android/os/SystemVibrator.java | 23 | ||||
| -rw-r--r-- | core/java/android/os/VibrationEffect.aidl | 4 | ||||
| -rw-r--r-- | core/java/android/os/VibrationEffect.java | 346 | ||||
| -rw-r--r-- | core/java/android/os/Vibrator.java | 143 | ||||
| -rw-r--r-- | services/core/java/com/android/server/VibratorService.java | 128 | ||||
| -rw-r--r-- | services/core/jni/com_android_server_VibratorService.cpp | 67 |
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)); } |