summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/api/test-current.txt34
-rw-r--r--core/java/android/os/CombinedVibration.java49
-rw-r--r--core/java/android/os/SystemVibrator.java3
-rw-r--r--core/java/android/os/SystemVibratorManager.java3
-rw-r--r--core/java/android/os/VibrationAttributes.java67
-rw-r--r--core/java/android/os/VibrationEffect.java53
-rw-r--r--core/java/android/os/Vibrator.java2
-rw-r--r--core/java/android/os/vibrator/PrebakedSegment.java29
-rw-r--r--core/java/android/os/vibrator/PrimitiveSegment.java11
-rw-r--r--core/java/android/os/vibrator/RampSegment.java12
-rw-r--r--core/java/android/os/vibrator/StepSegment.java11
-rw-r--r--core/java/android/os/vibrator/VibrationEffectSegment.java26
-rw-r--r--core/tests/coretests/src/android/os/CombinedVibrationTest.java83
-rw-r--r--core/tests/coretests/src/android/os/VibrationEffectTest.java101
-rw-r--r--core/tests/coretests/src/android/os/VibratorTest.java13
-rw-r--r--core/tests/coretests/src/android/os/vibrator/PrebakedSegmentTest.java46
-rw-r--r--core/tests/coretests/src/android/os/vibrator/PrimitiveSegmentTest.java24
-rw-r--r--core/tests/coretests/src/android/os/vibrator/RampSegmentTest.java12
-rw-r--r--core/tests/coretests/src/android/os/vibrator/StepSegmentTest.java12
-rw-r--r--services/core/java/com/android/server/notification/VibratorHelper.java2
-rw-r--r--services/core/java/com/android/server/vibrator/VibratorManagerService.java27
-rw-r--r--services/tests/servicestests/src/com/android/server/vibrator/VibratorManagerServiceTest.java87
22 files changed, 557 insertions, 150 deletions
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 4b7a9df97514..e52a1556a919 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -1835,11 +1835,6 @@ package android.os {
method public int getAudioUsage();
}
- public static final class VibrationAttributes.Builder {
- ctor public VibrationAttributes.Builder(@NonNull android.media.AudioAttributes, @NonNull android.os.VibrationEffect);
- ctor public VibrationAttributes.Builder(@NonNull android.os.VibrationAttributes, @NonNull android.os.VibrationEffect);
- }
-
public abstract class VibrationEffect implements android.os.Parcelable {
method public static android.os.VibrationEffect get(int);
method public static android.os.VibrationEffect get(int, boolean);
@@ -1856,13 +1851,9 @@ package android.os {
}
public static final class VibrationEffect.Composed extends android.os.VibrationEffect {
- method @NonNull public android.os.VibrationEffect.Composed applyEffectStrength(int);
method public long getDuration();
method public int getRepeatIndex();
method @NonNull public java.util.List<android.os.vibrator.VibrationEffectSegment> getSegments();
- method @NonNull public android.os.VibrationEffect.Composed resolve(int);
- method @NonNull public android.os.VibrationEffect.Composed scale(float);
- method public void validate();
method public void writeToParcel(@NonNull android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.os.VibrationEffect.Composed> CREATOR;
}
@@ -2010,72 +2001,47 @@ package android.os.strictmode {
package android.os.vibrator {
public final class PrebakedSegment extends android.os.vibrator.VibrationEffectSegment {
- method @NonNull public android.os.vibrator.PrebakedSegment applyEffectStrength(int);
method public int describeContents();
method public long getDuration();
method public int getEffectId();
method public int getEffectStrength();
- method public boolean hasNonZeroAmplitude();
- method @NonNull public android.os.vibrator.PrebakedSegment resolve(int);
- method @NonNull public android.os.vibrator.PrebakedSegment scale(float);
method public boolean shouldFallback();
- method public void validate();
method public void writeToParcel(@NonNull android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.os.vibrator.PrebakedSegment> CREATOR;
}
public final class PrimitiveSegment extends android.os.vibrator.VibrationEffectSegment {
- method @NonNull public android.os.vibrator.PrimitiveSegment applyEffectStrength(int);
method public int describeContents();
method public int getDelay();
method public long getDuration();
method public int getPrimitiveId();
method public float getScale();
- method public boolean hasNonZeroAmplitude();
- method @NonNull public android.os.vibrator.PrimitiveSegment resolve(int);
- method @NonNull public android.os.vibrator.PrimitiveSegment scale(float);
- method public void validate();
method public void writeToParcel(@NonNull android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.os.vibrator.PrimitiveSegment> CREATOR;
}
public final class RampSegment extends android.os.vibrator.VibrationEffectSegment {
- method @NonNull public android.os.vibrator.RampSegment applyEffectStrength(int);
method public int describeContents();
method public long getDuration();
method public float getEndAmplitude();
method public float getEndFrequency();
method public float getStartAmplitude();
method public float getStartFrequency();
- method public boolean hasNonZeroAmplitude();
- method @NonNull public android.os.vibrator.RampSegment resolve(int);
- method @NonNull public android.os.vibrator.RampSegment scale(float);
- method public void validate();
method public void writeToParcel(@NonNull android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.os.vibrator.RampSegment> CREATOR;
}
public final class StepSegment extends android.os.vibrator.VibrationEffectSegment {
- method @NonNull public android.os.vibrator.StepSegment applyEffectStrength(int);
method public int describeContents();
method public float getAmplitude();
method public long getDuration();
method public float getFrequency();
- method public boolean hasNonZeroAmplitude();
- method @NonNull public android.os.vibrator.StepSegment resolve(int);
- method @NonNull public android.os.vibrator.StepSegment scale(float);
- method public void validate();
method public void writeToParcel(@NonNull android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.os.vibrator.StepSegment> CREATOR;
}
public abstract class VibrationEffectSegment implements android.os.Parcelable {
- method @NonNull public abstract <T extends android.os.vibrator.VibrationEffectSegment> T applyEffectStrength(int);
method public abstract long getDuration();
- method public abstract boolean hasNonZeroAmplitude();
- method @NonNull public abstract <T extends android.os.vibrator.VibrationEffectSegment> T resolve(int);
- method @NonNull public abstract <T extends android.os.vibrator.VibrationEffectSegment> T scale(float);
- method public abstract void validate();
field @NonNull public static final android.os.Parcelable.Creator<android.os.vibrator.VibrationEffectSegment> CREATOR;
}
diff --git a/core/java/android/os/CombinedVibration.java b/core/java/android/os/CombinedVibration.java
index aff55aff39f7..5f2c11313940 100644
--- a/core/java/android/os/CombinedVibration.java
+++ b/core/java/android/os/CombinedVibration.java
@@ -110,6 +110,20 @@ public abstract class CombinedVibration implements Parcelable {
@TestApi
public abstract long getDuration();
+ /**
+ * Returns true if this effect could represent a touch haptic feedback.
+ *
+ * <p>It is strongly recommended that an instance of {@link VibrationAttributes} is specified
+ * for each vibration, with the correct usage. When a vibration is played with usage UNKNOWN,
+ * then this method will be used to classify the most common use case and make sure they are
+ * covered by the user settings for "Touch feedback".
+ *
+ * @hide
+ */
+ public boolean isHapticFeedbackCandidate() {
+ return false;
+ }
+
/** @hide */
public abstract void validate();
@@ -314,6 +328,12 @@ public abstract class CombinedVibration implements Parcelable {
/** @hide */
@Override
+ public boolean isHapticFeedbackCandidate() {
+ return mEffect.isHapticFeedbackCandidate();
+ }
+
+ /** @hide */
+ @Override
public void validate() {
mEffect.validate();
}
@@ -431,6 +451,17 @@ public abstract class CombinedVibration implements Parcelable {
/** @hide */
@Override
+ public boolean isHapticFeedbackCandidate() {
+ for (int i = 0; i < mEffects.size(); i++) {
+ if (!mEffects.valueAt(i).isHapticFeedbackCandidate()) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /** @hide */
+ @Override
public void validate() {
Preconditions.checkArgument(mEffects.size() > 0,
"There should be at least one effect set for a combined effect");
@@ -513,6 +544,9 @@ public abstract class CombinedVibration implements Parcelable {
*/
@TestApi
public static final class Sequential extends CombinedVibration {
+ // If a vibration is playing more than 3 effects, it's probably not haptic feedback
+ private static final long MAX_HAPTIC_FEEDBACK_SEQUENCE_SIZE = 3;
+
private final List<CombinedVibration> mEffects;
private final List<Integer> mDelays;
@@ -575,6 +609,21 @@ public abstract class CombinedVibration implements Parcelable {
/** @hide */
@Override
+ public boolean isHapticFeedbackCandidate() {
+ final int effectCount = mEffects.size();
+ if (effectCount > MAX_HAPTIC_FEEDBACK_SEQUENCE_SIZE) {
+ return false;
+ }
+ for (int i = 0; i < effectCount; i++) {
+ if (!mEffects.get(i).isHapticFeedbackCandidate()) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /** @hide */
+ @Override
public void validate() {
Preconditions.checkArgument(mEffects.size() > 0,
"There should be at least one effect set for a combined effect");
diff --git a/core/java/android/os/SystemVibrator.java b/core/java/android/os/SystemVibrator.java
index a82826889908..d0d6cb76280a 100644
--- a/core/java/android/os/SystemVibrator.java
+++ b/core/java/android/os/SystemVibrator.java
@@ -196,9 +196,6 @@ public class SystemVibrator extends Vibrator {
return;
}
CombinedVibration combinedEffect = CombinedVibration.createParallel(effect);
- // TODO(b/185351540): move this into VibratorManagerService once the touch vibration
- // heuristics is fixed and works for CombinedVibration. Make sure it's always applied.
- attributes = new VibrationAttributes.Builder(attributes, effect).build();
mVibratorManager.vibrate(uid, opPkg, combinedEffect, reason, attributes);
}
diff --git a/core/java/android/os/SystemVibratorManager.java b/core/java/android/os/SystemVibratorManager.java
index e5622a35b138..c690df2e3d31 100644
--- a/core/java/android/os/SystemVibratorManager.java
+++ b/core/java/android/os/SystemVibratorManager.java
@@ -223,9 +223,6 @@ public class SystemVibratorManager extends VibratorManager {
CombinedVibration combined = CombinedVibration.startParallel()
.addVibrator(mVibratorInfo.getId(), vibe)
.combine();
- // TODO(b/185351540): move this into VibratorManagerService once the touch vibration
- // heuristics is fixed and works for CombinedVibration. Make sure it's always applied.
- attributes = new VibrationAttributes.Builder(attributes, vibe).build();
SystemVibratorManager.this.vibrate(uid, opPkg, combined, reason, attributes);
}
diff --git a/core/java/android/os/VibrationAttributes.java b/core/java/android/os/VibrationAttributes.java
index 36cdfcc16f0f..9612ca6addcd 100644
--- a/core/java/android/os/VibrationAttributes.java
+++ b/core/java/android/os/VibrationAttributes.java
@@ -21,9 +21,6 @@ import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.TestApi;
import android.media.AudioAttributes;
-import android.os.vibrator.PrebakedSegment;
-import android.os.vibrator.VibrationEffectSegment;
-import android.util.Slog;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -156,9 +153,6 @@ public final class VibrationAttributes implements Parcelable {
*/
public static final int FLAG_ALL_SUPPORTED = FLAG_BYPASS_INTERRUPTION_POLICY;
- // If a vibration is playing for longer than 5s, it's probably not haptic feedback
- private static final long MAX_HAPTIC_FEEDBACK_DURATION = 5000;
-
/** Creates a new {@link VibrationAttributes} instance with given usage. */
public static @NonNull VibrationAttributes createForUsage(int usage) {
return new VibrationAttributes.Builder().setUsage(usage).build();
@@ -359,67 +353,6 @@ public final class VibrationAttributes implements Parcelable {
setFlags(audio);
}
- /**
- * Constructs a new Builder from AudioAttributes and a VibrationEffect to infer usage.
- * @hide
- */
- @TestApi
- public Builder(@NonNull AudioAttributes audio, @NonNull VibrationEffect effect) {
- this(audio);
- applyHapticFeedbackHeuristics(effect);
- }
-
- /**
- * Constructs a new Builder from VibrationAttributes and a VibrationEffect to infer usage.
- * @hide
- */
- @TestApi
- public Builder(@NonNull VibrationAttributes vib, @NonNull VibrationEffect effect) {
- this(vib);
- applyHapticFeedbackHeuristics(effect);
- }
-
- private void applyHapticFeedbackHeuristics(@Nullable VibrationEffect effect) {
- if (effect != null) {
- PrebakedSegment prebaked = extractPrebakedSegment(effect);
- if (mUsage == USAGE_UNKNOWN && prebaked != null) {
- switch (prebaked.getEffectId()) {
- case VibrationEffect.EFFECT_CLICK:
- case VibrationEffect.EFFECT_DOUBLE_CLICK:
- case VibrationEffect.EFFECT_HEAVY_CLICK:
- case VibrationEffect.EFFECT_TEXTURE_TICK:
- case VibrationEffect.EFFECT_TICK:
- case VibrationEffect.EFFECT_POP:
- case VibrationEffect.EFFECT_THUD:
- mUsage = USAGE_TOUCH;
- break;
- default:
- Slog.w(TAG, "Unknown prebaked vibration effect, assuming it isn't "
- + "haptic feedback");
- }
- }
- final long duration = effect.getDuration();
- if (mUsage == USAGE_UNKNOWN && duration >= 0
- && duration < MAX_HAPTIC_FEEDBACK_DURATION) {
- mUsage = USAGE_TOUCH;
- }
- }
- }
-
- @Nullable
- private PrebakedSegment extractPrebakedSegment(VibrationEffect effect) {
- if (effect instanceof VibrationEffect.Composed) {
- VibrationEffect.Composed composed = (VibrationEffect.Composed) effect;
- if (composed.getSegments().size() == 1) {
- VibrationEffectSegment segment = composed.getSegments().get(0);
- if (segment instanceof PrebakedSegment) {
- return (PrebakedSegment) segment;
- }
- }
- }
- return null;
- }
-
private void setUsage(@NonNull AudioAttributes audio) {
mOriginalAudioUsage = audio.getUsage();
switch (audio.getUsage()) {
diff --git a/core/java/android/os/VibrationEffect.java b/core/java/android/os/VibrationEffect.java
index a0cbbfe3327b..5758a4edec31 100644
--- a/core/java/android/os/VibrationEffect.java
+++ b/core/java/android/os/VibrationEffect.java
@@ -53,7 +53,10 @@ import java.util.Objects;
public abstract class VibrationEffect implements Parcelable {
// Stevens' coefficient to scale the perceived vibration intensity.
private static final float SCALE_GAMMA = 0.65f;
-
+ // If a vibration is playing for longer than 1s, it's probably not haptic feedback
+ private static final long MAX_HAPTIC_FEEDBACK_DURATION = 1000;
+ // If a vibration is playing more than 3 constants, it's probably not haptic feedback
+ private static final long MAX_HAPTIC_FEEDBACK_COMPOSITION_SIZE = 3;
/**
* The default vibration strength of the device.
@@ -439,6 +442,20 @@ public abstract class VibrationEffect implements Parcelable {
public abstract long getDuration();
/**
+ * Returns true if this effect could represent a touch haptic feedback.
+ *
+ * <p>It is strongly recommended that an instance of {@link VibrationAttributes} is specified
+ * for each vibration, with the correct usage. When a vibration is played with usage UNKNOWN,
+ * then this method will be used to classify the most common use case and make sure they are
+ * covered by the user settings for "Touch feedback".
+ *
+ * @hide
+ */
+ public boolean isHapticFeedbackCandidate() {
+ return false;
+ }
+
+ /**
* Resolve default values into integer amplitude numbers.
*
* @param defaultAmplitude the default amplitude to apply, must be between 0 and
@@ -582,6 +599,7 @@ public abstract class VibrationEffect implements Parcelable {
return mRepeatIndex;
}
+ /** @hide */
@Override
public void validate() {
int segmentCount = mSegments.size();
@@ -620,6 +638,37 @@ public abstract class VibrationEffect implements Parcelable {
return totalDuration;
}
+ /** @hide */
+ @Override
+ public boolean isHapticFeedbackCandidate() {
+ long totalDuration = getDuration();
+ if (totalDuration > MAX_HAPTIC_FEEDBACK_DURATION) {
+ // Vibration duration is known and is longer than the max duration used to classify
+ // haptic feedbacks (or repeating indefinitely with duration == Long.MAX_VALUE).
+ return false;
+ }
+ int segmentCount = mSegments.size();
+ if (segmentCount > MAX_HAPTIC_FEEDBACK_COMPOSITION_SIZE) {
+ // Vibration has some prebaked or primitive constants, it should be limited to the
+ // max composition size used to classify haptic feedbacks.
+ return false;
+ }
+ totalDuration = 0;
+ for (int i = 0; i < segmentCount; i++) {
+ if (!mSegments.get(i).isHapticFeedbackCandidate()) {
+ // There is at least one segment that is not a candidate for a haptic feedback.
+ return false;
+ }
+ long segmentDuration = mSegments.get(i).getDuration();
+ if (segmentDuration > 0) {
+ totalDuration += segmentDuration;
+ }
+ }
+ // Vibration might still have some ramp or step segments, check the known duration.
+ return totalDuration <= MAX_HAPTIC_FEEDBACK_DURATION;
+ }
+
+ /** @hide */
@NonNull
@Override
public Composed resolve(int defaultAmplitude) {
@@ -636,6 +685,7 @@ public abstract class VibrationEffect implements Parcelable {
return resolved;
}
+ /** @hide */
@NonNull
@Override
public Composed scale(float scaleFactor) {
@@ -652,6 +702,7 @@ public abstract class VibrationEffect implements Parcelable {
return scaled;
}
+ /** @hide */
@NonNull
@Override
public Composed applyEffectStrength(int effectStrength) {
diff --git a/core/java/android/os/Vibrator.java b/core/java/android/os/Vibrator.java
index 75234db0ea03..c67c82e37cd2 100644
--- a/core/java/android/os/Vibrator.java
+++ b/core/java/android/os/Vibrator.java
@@ -493,7 +493,7 @@ public abstract class Vibrator {
vibrate(vibe,
attributes == null
? new VibrationAttributes.Builder().build()
- : new VibrationAttributes.Builder(attributes, vibe).build());
+ : new VibrationAttributes.Builder(attributes).build());
}
/**
diff --git a/core/java/android/os/vibrator/PrebakedSegment.java b/core/java/android/os/vibrator/PrebakedSegment.java
index 78b43468d663..30f5a5ca86c5 100644
--- a/core/java/android/os/vibrator/PrebakedSegment.java
+++ b/core/java/android/os/vibrator/PrebakedSegment.java
@@ -67,17 +67,38 @@ public final class PrebakedSegment extends VibrationEffectSegment {
return -1;
}
+ /** @hide */
+ @Override
+ public boolean isHapticFeedbackCandidate() {
+ switch (mEffectId) {
+ case VibrationEffect.EFFECT_CLICK:
+ case VibrationEffect.EFFECT_DOUBLE_CLICK:
+ case VibrationEffect.EFFECT_HEAVY_CLICK:
+ case VibrationEffect.EFFECT_POP:
+ case VibrationEffect.EFFECT_TEXTURE_TICK:
+ case VibrationEffect.EFFECT_THUD:
+ case VibrationEffect.EFFECT_TICK:
+ return true;
+ default:
+ // VibrationEffect.RINGTONES are not segments that could represent a haptic feedback
+ return false;
+ }
+ }
+
+ /** @hide */
@Override
public boolean hasNonZeroAmplitude() {
return true;
}
+ /** @hide */
@NonNull
@Override
public PrebakedSegment resolve(int defaultAmplitude) {
return this;
}
+ /** @hide */
@NonNull
@Override
public PrebakedSegment scale(float scaleFactor) {
@@ -85,6 +106,7 @@ public final class PrebakedSegment extends VibrationEffectSegment {
return this;
}
+ /** @hide */
@NonNull
@Override
public PrebakedSegment applyEffectStrength(int effectStrength) {
@@ -105,16 +127,17 @@ public final class PrebakedSegment extends VibrationEffectSegment {
}
}
+ /** @hide */
@Override
public void validate() {
switch (mEffectId) {
case VibrationEffect.EFFECT_CLICK:
case VibrationEffect.EFFECT_DOUBLE_CLICK:
- case VibrationEffect.EFFECT_TICK:
+ case VibrationEffect.EFFECT_HEAVY_CLICK:
+ case VibrationEffect.EFFECT_POP:
case VibrationEffect.EFFECT_TEXTURE_TICK:
case VibrationEffect.EFFECT_THUD:
- case VibrationEffect.EFFECT_POP:
- case VibrationEffect.EFFECT_HEAVY_CLICK:
+ case VibrationEffect.EFFECT_TICK:
break;
default:
int[] ringtones = VibrationEffect.RINGTONES;
diff --git a/core/java/android/os/vibrator/PrimitiveSegment.java b/core/java/android/os/vibrator/PrimitiveSegment.java
index 2ef29cb26ebc..58ca97860737 100644
--- a/core/java/android/os/vibrator/PrimitiveSegment.java
+++ b/core/java/android/os/vibrator/PrimitiveSegment.java
@@ -67,18 +67,27 @@ public final class PrimitiveSegment extends VibrationEffectSegment {
return -1;
}
+ /** @hide */
+ @Override
+ public boolean isHapticFeedbackCandidate() {
+ return true;
+ }
+
+ /** @hide */
@Override
public boolean hasNonZeroAmplitude() {
// Every primitive plays a vibration with a non-zero amplitude, even at scale == 0.
return true;
}
+ /** @hide */
@NonNull
@Override
public PrimitiveSegment resolve(int defaultAmplitude) {
return this;
}
+ /** @hide */
@NonNull
@Override
public PrimitiveSegment scale(float scaleFactor) {
@@ -86,12 +95,14 @@ public final class PrimitiveSegment extends VibrationEffectSegment {
mDelay);
}
+ /** @hide */
@NonNull
@Override
public PrimitiveSegment applyEffectStrength(int effectStrength) {
return this;
}
+ /** @hide */
@Override
public void validate() {
Preconditions.checkArgumentInRange(mPrimitiveId, VibrationEffect.Composition.PRIMITIVE_NOOP,
diff --git a/core/java/android/os/vibrator/RampSegment.java b/core/java/android/os/vibrator/RampSegment.java
index aad87c5cd4e7..3ec56366d921 100644
--- a/core/java/android/os/vibrator/RampSegment.java
+++ b/core/java/android/os/vibrator/RampSegment.java
@@ -87,11 +87,19 @@ public final class RampSegment extends VibrationEffectSegment {
return mDuration;
}
+ /** @hide */
+ @Override
+ public boolean isHapticFeedbackCandidate() {
+ return true;
+ }
+
+ /** @hide */
@Override
public boolean hasNonZeroAmplitude() {
return mStartAmplitude > 0 || mEndAmplitude > 0;
}
+ /** @hide */
@Override
public void validate() {
Preconditions.checkArgumentNonnegative(mDuration,
@@ -100,7 +108,7 @@ public final class RampSegment extends VibrationEffectSegment {
Preconditions.checkArgumentInRange(mEndAmplitude, 0f, 1f, "endAmplitude");
}
-
+ /** @hide */
@NonNull
@Override
public RampSegment resolve(int defaultAmplitude) {
@@ -108,6 +116,7 @@ public final class RampSegment extends VibrationEffectSegment {
return this;
}
+ /** @hide */
@NonNull
@Override
public RampSegment scale(float scaleFactor) {
@@ -121,6 +130,7 @@ public final class RampSegment extends VibrationEffectSegment {
mDuration);
}
+ /** @hide */
@NonNull
@Override
public RampSegment applyEffectStrength(int effectStrength) {
diff --git a/core/java/android/os/vibrator/StepSegment.java b/core/java/android/os/vibrator/StepSegment.java
index 11209e0ee425..69a381f5c558 100644
--- a/core/java/android/os/vibrator/StepSegment.java
+++ b/core/java/android/os/vibrator/StepSegment.java
@@ -73,12 +73,20 @@ public final class StepSegment extends VibrationEffectSegment {
return mDuration;
}
+ /** @hide */
+ @Override
+ public boolean isHapticFeedbackCandidate() {
+ return true;
+ }
+
+ /** @hide */
@Override
public boolean hasNonZeroAmplitude() {
// DEFAULT_AMPLITUDE == -1 is still a non-zero amplitude that will be resolved later.
return Float.compare(mAmplitude, 0) != 0;
}
+ /** @hide */
@Override
public void validate() {
Preconditions.checkArgumentNonnegative(mDuration,
@@ -88,6 +96,7 @@ public final class StepSegment extends VibrationEffectSegment {
}
}
+ /** @hide */
@NonNull
@Override
public StepSegment resolve(int defaultAmplitude) {
@@ -103,6 +112,7 @@ public final class StepSegment extends VibrationEffectSegment {
mDuration);
}
+ /** @hide */
@NonNull
@Override
public StepSegment scale(float scaleFactor) {
@@ -113,6 +123,7 @@ public final class StepSegment extends VibrationEffectSegment {
mDuration);
}
+ /** @hide */
@NonNull
@Override
public StepSegment applyEffectStrength(int effectStrength) {
diff --git a/core/java/android/os/vibrator/VibrationEffectSegment.java b/core/java/android/os/vibrator/VibrationEffectSegment.java
index 5b42845cfa43..979c4472eb9b 100644
--- a/core/java/android/os/vibrator/VibrationEffectSegment.java
+++ b/core/java/android/os/vibrator/VibrationEffectSegment.java
@@ -57,10 +57,26 @@ public abstract class VibrationEffectSegment implements Parcelable {
*/
public abstract long getDuration();
- /** Returns true if this segment plays at a non-zero amplitude at some point. */
+ /**
+ * Returns true if this segment could be a haptic feedback effect candidate.
+ *
+ * @see VibrationEffect#isHapticFeedbackCandidate()
+ * @hide
+ */
+ public abstract boolean isHapticFeedbackCandidate();
+
+ /**
+ * Returns true if this segment plays at a non-zero amplitude at some point.
+ *
+ * @hide
+ */
public abstract boolean hasNonZeroAmplitude();
- /** Validates the segment, throwing exceptions if any parameter is invalid. */
+ /**
+ * Validates the segment, throwing exceptions if any parameter is invalid.
+ *
+ * @hide
+ */
public abstract void validate();
/**
@@ -68,6 +84,8 @@ public abstract class VibrationEffectSegment implements Parcelable {
*
* <p>This might fail with {@link IllegalArgumentException} if value is non-positive or larger
* than {@link VibrationEffect#MAX_AMPLITUDE}.
+ *
+ * @hide
*/
@NonNull
public abstract <T extends VibrationEffectSegment> T resolve(int defaultAmplitude);
@@ -77,6 +95,8 @@ public abstract class VibrationEffectSegment implements Parcelable {
*
* @param scaleFactor scale factor to be applied to the intensity. Values within [0,1) will
* scale down the intensity, values larger than 1 will scale up
+ *
+ * @hide
*/
@NonNull
public abstract <T extends VibrationEffectSegment> T scale(float scaleFactor);
@@ -86,6 +106,8 @@ public abstract class VibrationEffectSegment implements Parcelable {
*
* @param effectStrength new effect strength to be applied, one of
* VibrationEffect.EFFECT_STRENGTH_*.
+ *
+ * @hide
*/
@NonNull
public abstract <T extends VibrationEffectSegment> T applyEffectStrength(int effectStrength);
diff --git a/core/tests/coretests/src/android/os/CombinedVibrationTest.java b/core/tests/coretests/src/android/os/CombinedVibrationTest.java
index 06b5d180518f..508856ba4ae5 100644
--- a/core/tests/coretests/src/android/os/CombinedVibrationTest.java
+++ b/core/tests/coretests/src/android/os/CombinedVibrationTest.java
@@ -125,6 +125,12 @@ public class CombinedVibrationTest {
VibrationEffect.createOneShot(1, 1)).getDuration());
assertEquals(-1, CombinedVibration.createParallel(
VibrationEffect.get(VibrationEffect.EFFECT_CLICK)).getDuration());
+ assertEquals(-1, CombinedVibration.createParallel(
+ VibrationEffect.startComposition()
+ .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK)
+ .addPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK, 1, 100)
+ .compose())
+ .getDuration());
assertEquals(Long.MAX_VALUE, CombinedVibration.createParallel(
VibrationEffect.createWaveform(
new long[]{1, 2, 3}, new int[]{1, 2, 3}, 0)).getDuration());
@@ -175,6 +181,83 @@ public class CombinedVibrationTest {
}
@Test
+ public void testIsHapticFeedbackCandidateMono() {
+ assertTrue(CombinedVibration.createParallel(
+ VibrationEffect.createOneShot(1, 1)).isHapticFeedbackCandidate());
+ assertTrue(CombinedVibration.createParallel(
+ VibrationEffect.get(VibrationEffect.EFFECT_CLICK)).isHapticFeedbackCandidate());
+ assertTrue(CombinedVibration.createParallel(
+ VibrationEffect.startComposition()
+ .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK)
+ .addPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK, 1, 100)
+ .compose())
+ .isHapticFeedbackCandidate());
+ // Too long to be classified as a haptic feedback.
+ assertFalse(CombinedVibration.createParallel(
+ VibrationEffect.createOneShot(10_000, 1)).isHapticFeedbackCandidate());
+ // Repeating vibrations should not be classified as a haptic feedback.
+ assertFalse(CombinedVibration.createParallel(
+ VibrationEffect.createWaveform(new long[]{1}, new int[]{1}, 0))
+ .isHapticFeedbackCandidate());
+ }
+
+ @Test
+ public void testIsHapticFeedbackCandidateStereo() {
+ assertTrue(CombinedVibration.startParallel()
+ .addVibrator(1, VibrationEffect.createOneShot(1, 1))
+ .addVibrator(2, VibrationEffect.createWaveform(new long[]{6}, new int[]{1}, -1))
+ .combine()
+ .isHapticFeedbackCandidate());
+ assertTrue(CombinedVibration.startParallel()
+ .addVibrator(1, VibrationEffect.get(VibrationEffect.EFFECT_CLICK))
+ .addVibrator(2,
+ VibrationEffect.startComposition()
+ .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK)
+ .addPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK, 1, 100)
+ .compose())
+ .combine()
+ .isHapticFeedbackCandidate());
+ // Repeating vibrations should not be classified as a haptic feedback.
+ assertFalse(CombinedVibration.startParallel()
+ .addVibrator(1, VibrationEffect.get(VibrationEffect.EFFECT_CLICK))
+ .addVibrator(2, VibrationEffect.createWaveform(new long[]{1}, new int[]{1}, 0))
+ .combine()
+ .isHapticFeedbackCandidate());
+ }
+
+ @Test
+ public void testIsHapticFeedbackCandidateSequential() {
+ assertTrue(CombinedVibration.startSequential()
+ .addNext(1, VibrationEffect.createOneShot(10, 10), 10)
+ .addNext(2, VibrationEffect.createWaveform(new long[]{5}, new int[]{1}, -1))
+ .combine()
+ .isHapticFeedbackCandidate());
+ assertTrue(CombinedVibration.startSequential()
+ .addNext(1, VibrationEffect.get(VibrationEffect.EFFECT_CLICK))
+ .addNext(2,
+ VibrationEffect.startComposition()
+ .addPrimitive(VibrationEffect.Composition.PRIMITIVE_SLOW_RISE)
+ .addPrimitive(VibrationEffect.Composition.PRIMITIVE_QUICK_FALL)
+ .compose())
+ .combine()
+ .isHapticFeedbackCandidate());
+ // Repeating vibrations should not be classified as a haptic feedback.
+ assertFalse(CombinedVibration.startSequential()
+ .addNext(1, VibrationEffect.get(VibrationEffect.EFFECT_CLICK))
+ .addNext(2, VibrationEffect.createWaveform(new long[]{1}, new int[]{1}, 0))
+ .combine()
+ .isHapticFeedbackCandidate());
+ // Too many effects to be classified as a haptic feedback.
+ assertFalse(CombinedVibration.startSequential()
+ .addNext(1, VibrationEffect.get(VibrationEffect.EFFECT_CLICK))
+ .addNext(2, VibrationEffect.get(VibrationEffect.EFFECT_TICK))
+ .addNext(3, VibrationEffect.get(VibrationEffect.EFFECT_DOUBLE_CLICK))
+ .addNext(1, VibrationEffect.get(VibrationEffect.EFFECT_THUD))
+ .combine()
+ .isHapticFeedbackCandidate());
+ }
+
+ @Test
public void testHasVibratorMono_returnsTrueForAnyVibrator() {
CombinedVibration effect = CombinedVibration.createParallel(
VibrationEffect.get(VibrationEffect.EFFECT_CLICK));
diff --git a/core/tests/coretests/src/android/os/VibrationEffectTest.java b/core/tests/coretests/src/android/os/VibrationEffectTest.java
index 6cbfffc96116..781564b7be35 100644
--- a/core/tests/coretests/src/android/os/VibrationEffectTest.java
+++ b/core/tests/coretests/src/android/os/VibrationEffectTest.java
@@ -17,6 +17,7 @@
package android.os;
import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertNotNull;
import static junit.framework.Assert.assertNull;
import static junit.framework.Assert.assertTrue;
@@ -312,8 +313,106 @@ public class VibrationEffectTest {
assertTrue(100 / 255f > ((StepSegment) scaledDown.getSegments().get(1)).getAmplitude());
}
+
+ @Test
+ public void testDuration() {
+ assertEquals(1, VibrationEffect.createOneShot(1, 1).getDuration());
+ assertEquals(-1, VibrationEffect.get(VibrationEffect.EFFECT_CLICK).getDuration());
+ assertEquals(-1,
+ VibrationEffect.startComposition()
+ .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK)
+ .addPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK, 1, 100)
+ .compose()
+ .getDuration());
+ assertEquals(6, VibrationEffect.createWaveform(
+ new long[]{1, 2, 3}, new int[]{1, 2, 3}, -1).getDuration());
+ assertEquals(Long.MAX_VALUE, VibrationEffect.createWaveform(
+ new long[]{1, 2, 3}, new int[]{1, 2, 3}, 0).getDuration());
+ }
+
+ @Test
+ public void testIsHapticFeedbackCandidate_repeatingEffects_notCandidates() {
+ assertFalse(VibrationEffect.createWaveform(
+ new long[]{1, 2, 3}, new int[]{1, 2, 3}, 0).isHapticFeedbackCandidate());
+ }
+
+ @Test
+ public void testIsHapticFeedbackCandidate_longEffects_notCandidates() {
+ assertFalse(VibrationEffect.createOneShot(1500, 255).isHapticFeedbackCandidate());
+ assertFalse(VibrationEffect.createWaveform(
+ new long[]{200, 200, 700}, new int[]{1, 2, 3}, -1).isHapticFeedbackCandidate());
+ assertFalse(VibrationEffect.startWaveform()
+ .addRamp(1, 500)
+ .addStep(1, 200)
+ .addRamp(0, 500)
+ .build()
+ .isHapticFeedbackCandidate());
+ }
+
+ @Test
+ public void testIsHapticFeedbackCandidate_shortEffects_areCandidates() {
+ assertTrue(VibrationEffect.createOneShot(500, 255).isHapticFeedbackCandidate());
+ assertTrue(VibrationEffect.createWaveform(
+ new long[]{100, 200, 300}, new int[]{1, 2, 3}, -1).isHapticFeedbackCandidate());
+ assertTrue(VibrationEffect.startWaveform()
+ .addRamp(1, 300)
+ .addStep(1, 200)
+ .addRamp(0, 300)
+ .build()
+ .isHapticFeedbackCandidate());
+ }
+
+ @Test
+ public void testIsHapticFeedbackCandidate_longCompositions_notCandidates() {
+ assertFalse(VibrationEffect.startComposition()
+ .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK)
+ .addPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK)
+ .addPrimitive(VibrationEffect.Composition.PRIMITIVE_LOW_TICK)
+ .addPrimitive(VibrationEffect.Composition.PRIMITIVE_SPIN)
+ .addPrimitive(VibrationEffect.Composition.PRIMITIVE_THUD)
+ .addPrimitive(VibrationEffect.Composition.PRIMITIVE_SLOW_RISE)
+ .addPrimitive(VibrationEffect.Composition.PRIMITIVE_QUICK_FALL)
+ .compose()
+ .isHapticFeedbackCandidate());
+
+ assertFalse(VibrationEffect.startComposition()
+ .addEffect(VibrationEffect.createOneShot(1500, 255))
+ .addPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK)
+ .compose()
+ .isHapticFeedbackCandidate());
+ }
+
+ @Test
+ public void testIsHapticFeedbackCandidate_shortCompositions_areCandidates() {
+ assertTrue(VibrationEffect.startComposition()
+ .addPrimitive(VibrationEffect.Composition.PRIMITIVE_SLOW_RISE)
+ .addPrimitive(VibrationEffect.Composition.PRIMITIVE_QUICK_FALL)
+ .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK)
+ .compose()
+ .isHapticFeedbackCandidate());
+
+ assertTrue(VibrationEffect.startComposition()
+ .addEffect(VibrationEffect.createOneShot(100, 255))
+ .addPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK)
+ .compose()
+ .isHapticFeedbackCandidate());
+ }
+
+ @Test
+ public void testIsHapticFeedbackCandidate_prebakedRingtones_notCandidates() {
+ assertFalse(VibrationEffect.get(
+ VibrationEffect.RINGTONES[1]).isHapticFeedbackCandidate());
+ }
+
+ @Test
+ public void testIsHapticFeedbackCandidate_prebakedNotRingtoneConstants_areCandidates() {
+ assertTrue(VibrationEffect.get(VibrationEffect.EFFECT_CLICK).isHapticFeedbackCandidate());
+ assertTrue(VibrationEffect.get(VibrationEffect.EFFECT_THUD).isHapticFeedbackCandidate());
+ assertTrue(VibrationEffect.get(VibrationEffect.EFFECT_TICK).isHapticFeedbackCandidate());
+ }
+
private Resources mockRingtoneResources() {
- return mockRingtoneResources(new String[] {
+ return mockRingtoneResources(new String[]{
RINGTONE_URI_1,
RINGTONE_URI_2,
RINGTONE_URI_3
diff --git a/core/tests/coretests/src/android/os/VibratorTest.java b/core/tests/coretests/src/android/os/VibratorTest.java
index 6b69b3f6e33f..bdd76a5c162a 100644
--- a/core/tests/coretests/src/android/os/VibratorTest.java
+++ b/core/tests/coretests/src/android/os/VibratorTest.java
@@ -250,17 +250,4 @@ public class VibratorTest {
VibrationAttributes vibrationAttributes = captor.getValue();
assertEquals(new VibrationAttributes.Builder().build(), vibrationAttributes);
}
-
- @Test
- public void vibrate_withoutAudioAttributesAndLongEffect_hasUnknownUsage() {
- mVibratorSpy.vibrate(VibrationEffect.createOneShot(10_000, 255));
-
- ArgumentCaptor<VibrationAttributes> captor = ArgumentCaptor.forClass(
- VibrationAttributes.class);
- verify(mVibratorSpy).vibrate(anyInt(), anyString(), any(), isNull(), captor.capture());
-
- VibrationAttributes vibrationAttributes = captor.getValue();
- assertEquals(VibrationAttributes.USAGE_UNKNOWN, vibrationAttributes.getUsage());
- assertEquals(AudioAttributes.USAGE_UNKNOWN, vibrationAttributes.getAudioUsage());
- }
}
diff --git a/core/tests/coretests/src/android/os/vibrator/PrebakedSegmentTest.java b/core/tests/coretests/src/android/os/vibrator/PrebakedSegmentTest.java
index de80812f9c29..a0e1f437f1da 100644
--- a/core/tests/coretests/src/android/os/vibrator/PrebakedSegmentTest.java
+++ b/core/tests/coretests/src/android/os/vibrator/PrebakedSegmentTest.java
@@ -17,6 +17,7 @@
package android.os.vibrator;
import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertSame;
import static junit.framework.Assert.assertTrue;
@@ -105,4 +106,49 @@ public class PrebakedSegmentTest {
VibrationEffect.EFFECT_CLICK, true, VibrationEffect.EFFECT_STRENGTH_MEDIUM);
assertSame(prebaked, prebaked.scale(0.5f));
}
+
+ @Test
+ public void testDuration() {
+ assertEquals(-1, new PrebakedSegment(
+ VibrationEffect.EFFECT_CLICK, true, VibrationEffect.EFFECT_STRENGTH_MEDIUM)
+ .getDuration());
+ assertEquals(-1, new PrebakedSegment(
+ VibrationEffect.EFFECT_TICK, true, VibrationEffect.EFFECT_STRENGTH_MEDIUM)
+ .getDuration());
+ assertEquals(-1, new PrebakedSegment(
+ VibrationEffect.EFFECT_DOUBLE_CLICK, true, VibrationEffect.EFFECT_STRENGTH_MEDIUM)
+ .getDuration());
+ assertEquals(-1, new PrebakedSegment(
+ VibrationEffect.EFFECT_THUD, true, VibrationEffect.EFFECT_STRENGTH_MEDIUM)
+ .getDuration());
+ }
+
+ @Test
+ public void testIsHapticFeedbackCandidate_prebakedConstants_areCandidates() {
+ assertTrue(new PrebakedSegment(
+ VibrationEffect.EFFECT_CLICK, true, VibrationEffect.EFFECT_STRENGTH_MEDIUM)
+ .isHapticFeedbackCandidate());
+ assertTrue(new PrebakedSegment(
+ VibrationEffect.EFFECT_TICK, true, VibrationEffect.EFFECT_STRENGTH_MEDIUM)
+ .isHapticFeedbackCandidate());
+ assertTrue(new PrebakedSegment(
+ VibrationEffect.EFFECT_DOUBLE_CLICK, true, VibrationEffect.EFFECT_STRENGTH_MEDIUM)
+ .isHapticFeedbackCandidate());
+ assertTrue(new PrebakedSegment(
+ VibrationEffect.EFFECT_HEAVY_CLICK, true, VibrationEffect.EFFECT_STRENGTH_MEDIUM)
+ .isHapticFeedbackCandidate());
+ assertTrue(new PrebakedSegment(
+ VibrationEffect.EFFECT_THUD, true, VibrationEffect.EFFECT_STRENGTH_MEDIUM)
+ .isHapticFeedbackCandidate());
+ assertTrue(new PrebakedSegment(
+ VibrationEffect.EFFECT_TEXTURE_TICK, true, VibrationEffect.EFFECT_STRENGTH_MEDIUM)
+ .isHapticFeedbackCandidate());
+ }
+
+ @Test
+ public void testIsHapticFeedbackCandidate_prebakedRingtones_notCandidates() {
+ assertFalse(new PrebakedSegment(
+ VibrationEffect.RINGTONES[1], true, VibrationEffect.EFFECT_STRENGTH_MEDIUM)
+ .isHapticFeedbackCandidate());
+ }
}
diff --git a/core/tests/coretests/src/android/os/vibrator/PrimitiveSegmentTest.java b/core/tests/coretests/src/android/os/vibrator/PrimitiveSegmentTest.java
index 538655bb394b..a69055335663 100644
--- a/core/tests/coretests/src/android/os/vibrator/PrimitiveSegmentTest.java
+++ b/core/tests/coretests/src/android/os/vibrator/PrimitiveSegmentTest.java
@@ -127,4 +127,28 @@ public class PrimitiveSegmentTest {
assertEquals(0f, initial.scale(1.5f).scale(2 / 3f).getScale(), TOLERANCE);
assertEquals(0f, initial.scale(0.8f).scale(1.25f).getScale(), TOLERANCE);
}
+
+ @Test
+ public void testDuration() {
+ assertEquals(-1, new PrimitiveSegment(
+ VibrationEffect.Composition.PRIMITIVE_NOOP, 1, 10).getDuration());
+ assertEquals(-1, new PrimitiveSegment(
+ VibrationEffect.Composition.PRIMITIVE_CLICK, 1, 100).getDuration());
+ assertEquals(-1, new PrimitiveSegment(
+ VibrationEffect.Composition.PRIMITIVE_SPIN, 1, 0).getDuration());
+ }
+
+ @Test
+ public void testIsHapticFeedbackCandidate_returnsTrue() {
+ assertTrue(new PrimitiveSegment(
+ VibrationEffect.Composition.PRIMITIVE_NOOP, 1, 10).isHapticFeedbackCandidate());
+ assertTrue(new PrimitiveSegment(
+ VibrationEffect.Composition.PRIMITIVE_CLICK, 1, 10).isHapticFeedbackCandidate());
+ assertTrue(new PrimitiveSegment(
+ VibrationEffect.Composition.PRIMITIVE_TICK, 1, 10).isHapticFeedbackCandidate());
+ assertTrue(new PrimitiveSegment(
+ VibrationEffect.Composition.PRIMITIVE_THUD, 1, 10).isHapticFeedbackCandidate());
+ assertTrue(new PrimitiveSegment(
+ VibrationEffect.Composition.PRIMITIVE_SPIN, 1, 10).isHapticFeedbackCandidate());
+ }
}
diff --git a/core/tests/coretests/src/android/os/vibrator/RampSegmentTest.java b/core/tests/coretests/src/android/os/vibrator/RampSegmentTest.java
index 174b4a76b3c3..5f80d2a10515 100644
--- a/core/tests/coretests/src/android/os/vibrator/RampSegmentTest.java
+++ b/core/tests/coretests/src/android/os/vibrator/RampSegmentTest.java
@@ -125,4 +125,16 @@ public class RampSegmentTest {
assertEquals(0.35f, initial.scale(0.8f).getStartAmplitude(), TOLERANCE);
assertEquals(0.5f, initial.scale(0.8f).scale(1.25f).getStartAmplitude(), TOLERANCE);
}
+
+ @Test
+ public void testDuration() {
+ assertEquals(10, new RampSegment(0.5f, 1, 0, 0, 10).getDuration());
+ }
+
+ @Test
+ public void testIsHapticFeedbackCandidate_returnsTrue() {
+ // A single ramp segment duration is not checked here, but contributes to the effect known
+ // duration checked in VibrationEffect implementations.
+ assertTrue(new RampSegment(0.5f, 1, 0, 0, 5_000).isHapticFeedbackCandidate());
+ }
}
diff --git a/core/tests/coretests/src/android/os/vibrator/StepSegmentTest.java b/core/tests/coretests/src/android/os/vibrator/StepSegmentTest.java
index 79529b8cb13c..fdce86a27ac4 100644
--- a/core/tests/coretests/src/android/os/vibrator/StepSegmentTest.java
+++ b/core/tests/coretests/src/android/os/vibrator/StepSegmentTest.java
@@ -141,4 +141,16 @@ public class StepSegmentTest {
assertEquals(VibrationEffect.DEFAULT_AMPLITUDE, initial.scale(1.5f).getAmplitude(),
TOLERANCE);
}
+
+ @Test
+ public void testDuration() {
+ assertEquals(5, new StepSegment(0, 0, 5).getDuration());
+ }
+
+ @Test
+ public void testIsHapticFeedbackCandidate_returnsTrue() {
+ // A single step segment duration is not checked here, but contributes to the effect known
+ // duration checked in VibrationEffect implementations.
+ assertTrue(new StepSegment(0, 0, 5_000).isHapticFeedbackCandidate());
+ }
}
diff --git a/services/core/java/com/android/server/notification/VibratorHelper.java b/services/core/java/com/android/server/notification/VibratorHelper.java
index 5199ef68fb7f..be5f2194997a 100644
--- a/services/core/java/com/android/server/notification/VibratorHelper.java
+++ b/services/core/java/com/android/server/notification/VibratorHelper.java
@@ -89,7 +89,7 @@ public final class VibratorHelper {
*/
public void vibrate(VibrationEffect effect, AudioAttributes attrs, String reason) {
mVibrator.vibrate(Process.SYSTEM_UID, PackageManagerService.PLATFORM_PACKAGE_NAME,
- effect, reason, new VibrationAttributes.Builder(attrs, effect).build());
+ effect, reason, new VibrationAttributes.Builder(attrs).build());
}
/** Stop all notification vibrations (ringtone, alarm, notification usages). */
diff --git a/services/core/java/com/android/server/vibrator/VibratorManagerService.java b/services/core/java/com/android/server/vibrator/VibratorManagerService.java
index 5d40c23610dd..97172015f9f0 100644
--- a/services/core/java/com/android/server/vibrator/VibratorManagerService.java
+++ b/services/core/java/com/android/server/vibrator/VibratorManagerService.java
@@ -341,7 +341,7 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
if (!isEffectValid(effect)) {
return false;
}
- attrs = fixupVibrationAttributes(attrs);
+ attrs = fixupVibrationAttributes(attrs, effect);
synchronized (mLock) {
SparseArray<PrebakedSegment> effects = fixupAlwaysOnEffectsLocked(effect);
if (effects == null) {
@@ -385,7 +385,7 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
if (!isEffectValid(effect)) {
return null;
}
- attrs = fixupVibrationAttributes(attrs);
+ attrs = fixupVibrationAttributes(attrs, effect);
Vibration vib = new Vibration(token, mNextVibrationId.getAndIncrement(), effect, attrs,
uid, opPkg, reason);
fillVibrationFallbacks(vib, effect);
@@ -895,21 +895,32 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
* Return new {@link VibrationAttributes} that only applies flags that this user has permissions
* to use.
*/
- private VibrationAttributes fixupVibrationAttributes(@Nullable VibrationAttributes attrs) {
+ @NonNull
+ private VibrationAttributes fixupVibrationAttributes(@Nullable VibrationAttributes attrs,
+ CombinedVibration effect) {
if (attrs == null) {
attrs = DEFAULT_ATTRIBUTES;
}
+ int usage = attrs.getUsage();
+ if ((usage == VibrationAttributes.USAGE_UNKNOWN) && effect.isHapticFeedbackCandidate()) {
+ usage = VibrationAttributes.USAGE_TOUCH;
+ }
+ int flags = attrs.getFlags();
if (attrs.isFlagSet(VibrationAttributes.FLAG_BYPASS_INTERRUPTION_POLICY)) {
if (!(hasPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
|| hasPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
|| hasPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING))) {
- final int flags = attrs.getFlags()
- & ~VibrationAttributes.FLAG_BYPASS_INTERRUPTION_POLICY;
- attrs = new VibrationAttributes.Builder(attrs)
- .setFlags(flags, attrs.getFlags()).build();
+ // Remove bypass policy flag from attributes if the app does not have permissions.
+ flags &= ~VibrationAttributes.FLAG_BYPASS_INTERRUPTION_POLICY;
}
}
- return attrs;
+ if ((usage == attrs.getUsage()) && (flags == attrs.getFlags())) {
+ return attrs;
+ }
+ return new VibrationAttributes.Builder(attrs)
+ .setUsage(usage)
+ .setFlags(flags, attrs.getFlags())
+ .build();
}
@GuardedBy("mLock")
diff --git a/services/tests/servicestests/src/com/android/server/vibrator/VibratorManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/vibrator/VibratorManagerServiceTest.java
index 4eb9c06d5e8f..c0f75966bcf2 100644
--- a/services/tests/servicestests/src/com/android/server/vibrator/VibratorManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/vibrator/VibratorManagerServiceTest.java
@@ -126,15 +126,23 @@ public class VibratorManagerServiceTest {
new VibrationAttributes.Builder().setUsage(
VibrationAttributes.USAGE_RINGTONE).build();
- @Rule public MockitoRule rule = MockitoJUnit.rule();
- @Rule public FakeSettingsProviderRule mSettingsProviderRule = FakeSettingsProvider.rule();
-
- @Mock private VibratorManagerService.NativeWrapper mNativeWrapperMock;
- @Mock private PackageManagerInternal mPackageManagerInternalMock;
- @Mock private PowerManagerInternal mPowerManagerInternalMock;
- @Mock private PowerSaveState mPowerSaveStateMock;
- @Mock private AppOpsManager mAppOpsManagerMock;
- @Mock private IInputManager mIInputManagerMock;
+ @Rule
+ public MockitoRule rule = MockitoJUnit.rule();
+ @Rule
+ public FakeSettingsProviderRule mSettingsProviderRule = FakeSettingsProvider.rule();
+
+ @Mock
+ private VibratorManagerService.NativeWrapper mNativeWrapperMock;
+ @Mock
+ private PackageManagerInternal mPackageManagerInternalMock;
+ @Mock
+ private PowerManagerInternal mPowerManagerInternalMock;
+ @Mock
+ private PowerSaveState mPowerSaveStateMock;
+ @Mock
+ private AppOpsManager mAppOpsManagerMock;
+ @Mock
+ private IInputManager mIInputManagerMock;
private final Map<Integer, FakeVibratorControllerProvider> mVibratorProviders = new HashMap<>();
@@ -397,6 +405,7 @@ public class VibratorManagerServiceTest {
@Test
public void registerVibratorStateListener_multipleVibratorsAreTriggered() throws Exception {
mockVibrators(0, 1, 2);
+ mVibratorProviders.get(1).setSupportedEffects(VibrationEffect.EFFECT_CLICK);
VibratorManagerService service = createSystemReadyService();
IVibratorStateListener[] listeners = new IVibratorStateListener[3];
for (int i = 0; i < 3; i++) {
@@ -601,8 +610,8 @@ public class VibratorManagerServiceTest {
VibrationEffect effect = VibrationEffect.get(VibrationEffect.EFFECT_CLICK);
AudioAttributes audioAttributes = new AudioAttributes.Builder()
.setUsage(AudioAttributes.USAGE_ASSISTANCE_ACCESSIBILITY).build();
- VibrationAttributes vibrationAttributes = new VibrationAttributes.Builder(
- audioAttributes, effect).build();
+ VibrationAttributes vibrationAttributes =
+ new VibrationAttributes.Builder(audioAttributes).build();
vibrate(service, effect, vibrationAttributes);
@@ -621,7 +630,7 @@ public class VibratorManagerServiceTest {
vibrate(service, VibrationEffect.get(VibrationEffect.EFFECT_CLICK),
new VibrationAttributes.Builder().setUsage(
VibrationAttributes.USAGE_COMMUNICATION_REQUEST).build());
- vibrate(service, VibrationEffect.get(VibrationEffect.EFFECT_TICK),
+ vibrate(service, VibrationEffect.createOneShot(2000, 200),
new VibrationAttributes.Builder().setUsage(
VibrationAttributes.USAGE_UNKNOWN).build());
@@ -642,6 +651,60 @@ public class VibratorManagerServiceTest {
}
@Test
+ public void vibrate_withAttributesUnknownUsage_usesEffectToIdentifyTouchUsage() {
+ VibratorManagerService service = createSystemReadyService();
+
+ VibrationAttributes unknownAttributes = VibrationAttributes.createForUsage(
+ VibrationAttributes.USAGE_UNKNOWN);
+ vibrate(service, VibrationEffect.get(VibrationEffect.EFFECT_CLICK), unknownAttributes);
+ vibrate(service, VibrationEffect.createOneShot(200, 200), unknownAttributes);
+ vibrate(service, VibrationEffect.createWaveform(
+ new long[] { 100, 200, 300 }, new int[] {1, 2, 3}, -1), unknownAttributes);
+ vibrate(service,
+ VibrationEffect.startComposition()
+ .addPrimitive(VibrationEffect.Composition.PRIMITIVE_QUICK_RISE)
+ .addPrimitive(VibrationEffect.Composition.PRIMITIVE_QUICK_FALL)
+ .compose(),
+ unknownAttributes);
+
+ verify(mAppOpsManagerMock, times(4))
+ .checkAudioOpNoThrow(eq(AppOpsManager.OP_VIBRATE),
+ eq(AudioAttributes.USAGE_ASSISTANCE_SONIFICATION), anyInt(), anyString());
+ verify(mAppOpsManagerMock, never())
+ .checkAudioOpNoThrow(eq(AppOpsManager.OP_VIBRATE),
+ eq(AudioAttributes.USAGE_UNKNOWN), anyInt(), anyString());
+ }
+
+ @Test
+ public void vibrate_withAttributesUnknownUsage_ignoresEffectIfNotHapticFeedbackCandidate() {
+ VibratorManagerService service = createSystemReadyService();
+
+ VibrationAttributes unknownAttributes = VibrationAttributes.createForUsage(
+ VibrationAttributes.USAGE_UNKNOWN);
+ vibrate(service, VibrationEffect.get(VibrationEffect.RINGTONES[0]), unknownAttributes);
+ vibrate(service, VibrationEffect.createOneShot(2000, 200), unknownAttributes);
+ vibrate(service, VibrationEffect.createWaveform(
+ new long[] { 100, 200, 300 }, new int[] {1, 2, 3}, 0), unknownAttributes);
+ vibrate(service,
+ VibrationEffect.startComposition()
+ .addPrimitive(VibrationEffect.Composition.PRIMITIVE_QUICK_RISE)
+ .addPrimitive(VibrationEffect.Composition.PRIMITIVE_QUICK_FALL)
+ .addPrimitive(VibrationEffect.Composition.PRIMITIVE_SPIN)
+ .addPrimitive(VibrationEffect.Composition.PRIMITIVE_THUD)
+ .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK)
+ .addPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK)
+ .compose(),
+ unknownAttributes);
+
+ verify(mAppOpsManagerMock, never())
+ .checkAudioOpNoThrow(eq(AppOpsManager.OP_VIBRATE),
+ eq(AudioAttributes.USAGE_ASSISTANCE_SONIFICATION), anyInt(), anyString());
+ verify(mAppOpsManagerMock, times(4))
+ .checkAudioOpNoThrow(eq(AppOpsManager.OP_VIBRATE),
+ eq(AudioAttributes.USAGE_UNKNOWN), anyInt(), anyString());
+ }
+
+ @Test
public void vibrate_withOngoingRepeatingVibration_ignoresEffect() throws Exception {
mockVibrators(1);
mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);