From 3680feb9036378ef1d638f6b248d02401b25b962 Mon Sep 17 00:00:00 2001 From: Lais Andrade Date: Fri, 7 Jan 2022 17:15:53 +0000 Subject: [PATCH] Move PWLE notification vibration pattern to config.xml The generic pattern coded in the notification service ignores the user settings for vibration intensity because of the current PWLE implementation in Android S. Moving this to a device-specific configuration (empty by default). Each device-specific pattern should be designed to accomodate the vibrator frequency response curve that will be exposed by the Vibrator API. Fix: 212366218 Test: VibratorHelperTest + manual Change-Id: I8807ede4a13e4880f51a0075ff7c658724649265 Merged-In: I8807ede4a13e4880f51a0075ff7c658724649265 --- core/res/res/values/config.xml | 20 ++++ core/res/res/values/symbols.xml | 2 + .../server/notification/VibratorHelper.java | 98 ++++++++++++++----- .../notification/VibratorHelperTest.java | 12 +++ 4 files changed, 108 insertions(+), 24 deletions(-) diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index 7bedcc67dc6d..4334476b82a6 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -2679,6 +2679,16 @@ 350 + + + + + + + false diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 46b249e5be6d..1a215b6696a9 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -1917,7 +1917,9 @@ + + diff --git a/services/core/java/com/android/server/notification/VibratorHelper.java b/services/core/java/com/android/server/notification/VibratorHelper.java index 0a69aec76306..449fae13f137 100644 --- a/services/core/java/com/android/server/notification/VibratorHelper.java +++ b/services/core/java/com/android/server/notification/VibratorHelper.java @@ -19,6 +19,7 @@ package com.android.server.notification; import android.annotation.Nullable; import android.content.Context; import android.content.res.Resources; +import android.content.res.TypedArray; import android.media.AudioAttributes; import android.os.Process; import android.os.VibrationAttributes; @@ -39,18 +40,16 @@ public final class VibratorHelper { private static final long[] DEFAULT_VIBRATE_PATTERN = {0, 250, 250, 250}; private static final int VIBRATE_PATTERN_MAXLEN = 8 * 2 + 1; // up to eight bumps - private static final int CHIRP_LEVEL_DURATION_MILLIS = 100; - private static final int DEFAULT_CHIRP_RAMP_DURATION_MILLIS = 100; - private static final int FALLBACK_CHIRP_RAMP_DURATION_MILLIS = 50; private final Vibrator mVibrator; private final long[] mDefaultPattern; private final long[] mFallbackPattern; + @Nullable private final float[] mDefaultPwlePattern; + @Nullable private final float[] mFallbackPwlePattern; public VibratorHelper(Context context) { mVibrator = context.getSystemService(Vibrator.class); - mDefaultPattern = getLongArray( - context.getResources(), + mDefaultPattern = getLongArray(context.getResources(), com.android.internal.R.array.config_defaultNotificationVibePattern, VIBRATE_PATTERN_MAXLEN, DEFAULT_VIBRATE_PATTERN); @@ -58,6 +57,10 @@ public final class VibratorHelper { R.array.config_notificationFallbackVibePattern, VIBRATE_PATTERN_MAXLEN, DEFAULT_VIBRATE_PATTERN); + mDefaultPwlePattern = getFloatArray(context.getResources(), + com.android.internal.R.array.config_defaultNotificationVibeWaveform); + mFallbackPwlePattern = getFloatArray(context.getResources(), + com.android.internal.R.array.config_notificationFallbackVibeWaveform); } /** @@ -82,6 +85,50 @@ public final class VibratorHelper { return null; } + /** + * Safely create a {@link VibrationEffect} from given waveform description. + * + *

The waveform is described by a sequence of values for target amplitude, frequency and + * duration, that are forwarded to + * {@link VibrationEffect.WaveformBuilder#addRamp(float, float, int)}. + * + *

This method returns {@code null} if the pattern is also {@code null} or invalid. + * + * @param values The list of values describing the waveform as a sequence of target amplitude, + * frequency and duration. + * @param insistent {@code true} if the vibration should loop until it is cancelled. + */ + @Nullable + public static VibrationEffect createPwleWaveformVibration(@Nullable float[] values, + boolean insistent) { + try { + if (values == null) { + return null; + } + + int length = values.length; + // The waveform is described by triples (amplitude, frequency, duration) + if ((length == 0) || (length % 3 != 0)) { + return null; + } + + VibrationEffect.WaveformBuilder waveformBuilder = VibrationEffect.startWaveform(); + for (int i = 0; i < length; i += 3) { + waveformBuilder.addRamp(/* amplitude= */ values[i], /* frequency= */ values[i + 1], + /* duration= */ (int) values[i + 2]); + } + + if (insistent) { + return waveformBuilder.build(/* repeat= */ 0); + } + return waveformBuilder.build(); + } catch (IllegalArgumentException e) { + Slog.e(TAG, "Error creating vibration PWLE waveform with pattern: " + + Arrays.toString(values)); + } + return null; + } + /** * Vibrate the device with given {@code effect}. * @@ -106,7 +153,10 @@ public final class VibratorHelper { */ public VibrationEffect createFallbackVibration(boolean insistent) { if (mVibrator.hasFrequencyControl()) { - return createChirpVibration(FALLBACK_CHIRP_RAMP_DURATION_MILLIS, insistent); + VibrationEffect effect = createPwleWaveformVibration(mFallbackPwlePattern, insistent); + if (effect != null) { + return effect; + } } return createWaveformVibration(mFallbackPattern, insistent); } @@ -118,29 +168,29 @@ public final class VibratorHelper { */ public VibrationEffect createDefaultVibration(boolean insistent) { if (mVibrator.hasFrequencyControl()) { - return createChirpVibration(DEFAULT_CHIRP_RAMP_DURATION_MILLIS, insistent); + VibrationEffect effect = createPwleWaveformVibration(mDefaultPwlePattern, insistent); + if (effect != null) { + return effect; + } } return createWaveformVibration(mDefaultPattern, insistent); } - private static VibrationEffect createChirpVibration(int rampDuration, boolean insistent) { - VibrationEffect.WaveformBuilder waveformBuilder = VibrationEffect.startWaveform() - .addStep(/* amplitude= */ 0, /* frequency= */ -0.85f, /* duration= */ 0) - .addRamp(/* amplitude= */ 1, /* frequency= */ -0.25f, rampDuration) - .addStep(/* amplitude= */ 1, /* frequency= */ -0.25f, CHIRP_LEVEL_DURATION_MILLIS) - .addRamp(/* amplitude= */ 0, /* frequency= */ -0.85f, rampDuration); - - if (insistent) { - return waveformBuilder - .addStep(/* amplitude= */ 0, CHIRP_LEVEL_DURATION_MILLIS) - .build(/* repeat= */ 0); + @Nullable + private static float[] getFloatArray(Resources resources, int resId) { + TypedArray array = resources.obtainTypedArray(resId); + try { + float[] values = new float[array.length()]; + for (int i = 0; i < values.length; i++) { + values[i] = array.getFloat(i, Float.NaN); + if (Float.isNaN(values[i])) { + return null; + } + } + return values; + } finally { + array.recycle(); } - - VibrationEffect singleBeat = waveformBuilder.build(); - return VibrationEffect.startComposition() - .addEffect(singleBeat) - .addEffect(singleBeat, /* delay= */ CHIRP_LEVEL_DURATION_MILLIS) - .compose(); } private static long[] getLongArray(Resources resources, int resId, int maxLength, long[] def) { diff --git a/services/tests/uiservicestests/src/com/android/server/notification/VibratorHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/VibratorHelperTest.java index c77a474e032c..8bc0c6c65fa3 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/VibratorHelperTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/VibratorHelperTest.java @@ -40,7 +40,10 @@ import org.mockito.MockitoAnnotations; @RunWith(AndroidJUnit4.class) public class VibratorHelperTest extends UiServiceTestCase { + // OFF/ON vibration pattern private static final long[] CUSTOM_PATTERN = new long[] { 100, 200, 300, 400 }; + // (amplitude, frequency, duration) triples list + private static final float[] PWLE_PATTERN = new float[] { 1, 0, 100 }; @Mock private Vibrator mVibrator; @@ -58,12 +61,16 @@ public class VibratorHelperTest extends UiServiceTestCase { public void createWaveformVibration_insistent_createsRepeatingVibration() { assertRepeatingVibration( VibratorHelper.createWaveformVibration(CUSTOM_PATTERN, /* insistent= */ true)); + assertRepeatingVibration( + VibratorHelper.createPwleWaveformVibration(PWLE_PATTERN, /* insistent= */ true)); } @Test public void createWaveformVibration_nonInsistent_createsSingleShotVibration() { assertSingleVibration( VibratorHelper.createWaveformVibration(CUSTOM_PATTERN, /* insistent= */ false)); + assertSingleVibration( + VibratorHelper.createPwleWaveformVibration(PWLE_PATTERN, /* insistent= */ false)); } @Test @@ -71,6 +78,11 @@ public class VibratorHelperTest extends UiServiceTestCase { assertNull(VibratorHelper.createWaveformVibration(null, false)); assertNull(VibratorHelper.createWaveformVibration(new long[0], false)); assertNull(VibratorHelper.createWaveformVibration(new long[] { 0, 0 }, false)); + + assertNull(VibratorHelper.createPwleWaveformVibration(null, false)); + assertNull(VibratorHelper.createPwleWaveformVibration(new float[0], false)); + assertNull(VibratorHelper.createPwleWaveformVibration(new float[] { 0 }, false)); + assertNull(VibratorHelper.createPwleWaveformVibration(new float[] { 0, 0, 0 }, false)); } @Test