diff options
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); |