Loading core/res/res/values/config.xml +20 −0 Original line number Diff line number Diff line Loading @@ -2679,6 +2679,16 @@ <item>350</item> </integer-array> <!-- A vibration waveform for notifications that specify DEFAULT_VIBRATE. This value is a float array with values grouped as { targetAmplitude (within [0,1]), targetFrequency [-1,1], duration (in milliseconds) } This is only applied on devices with vibration frequency control. If the device doesn't support frequency control, then the vibration specified in config_defaultNotificationVibePattern is used instead. --> <array name="config_defaultNotificationVibeWaveform"> </array> <!-- Vibrator pattern to be used as the default for notifications that do not specify vibration but vibrate anyway because the device is in vibrate mode. Loading @@ -2690,6 +2700,16 @@ <item>100</item> </integer-array> <!-- A vibration waveform for notifications that do not specify vibration but vibrate anyway, because the device is in vibrate mode. This value is a float array with values grouped as { targetAmplitude (within [0,1]), targetFrequency [-1,1], duration (in milliseconds) } This is only applied on devices with vibration frequency control. If the device doesn't support frequency control, then the vibration specified in config_notificationFallbackVibePattern is used instead. --> <array name="config_notificationFallbackVibeWaveform"> </array> <!-- Flag indicating if the speed up audio on mt call code should be executed --> <bool name="config_speed_up_audio_on_mt_calls">false</bool> Loading core/res/res/values/symbols.xml +2 −0 Original line number Diff line number Diff line Loading @@ -1917,7 +1917,9 @@ <java-symbol type="array" name="config_locationExtraPackageNames" /> <java-symbol type="array" name="config_testLocationProviders" /> <java-symbol type="array" name="config_defaultNotificationVibePattern" /> <java-symbol type="array" name="config_defaultNotificationVibeWaveform" /> <java-symbol type="array" name="config_notificationFallbackVibePattern" /> <java-symbol type="array" name="config_notificationFallbackVibeWaveform" /> <java-symbol type="bool" name="config_enableServerNotificationEffectsForAutomotive" /> <java-symbol type="bool" name="config_useAttentionLight" /> <java-symbol type="bool" name="config_adaptive_sleep_available" /> Loading services/core/java/com/android/server/notification/VibratorHelper.java +74 −24 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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); Loading @@ -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); } /** Loading @@ -82,6 +85,50 @@ public final class VibratorHelper { return null; } /** * Safely create a {@link VibrationEffect} from given waveform description. * * <p>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)}. * * <p>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}. * Loading @@ -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); } Loading @@ -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) { Loading services/tests/uiservicestests/src/com/android/server/notification/VibratorHelperTest.java +12 −0 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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 Loading @@ -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 Loading Loading
core/res/res/values/config.xml +20 −0 Original line number Diff line number Diff line Loading @@ -2679,6 +2679,16 @@ <item>350</item> </integer-array> <!-- A vibration waveform for notifications that specify DEFAULT_VIBRATE. This value is a float array with values grouped as { targetAmplitude (within [0,1]), targetFrequency [-1,1], duration (in milliseconds) } This is only applied on devices with vibration frequency control. If the device doesn't support frequency control, then the vibration specified in config_defaultNotificationVibePattern is used instead. --> <array name="config_defaultNotificationVibeWaveform"> </array> <!-- Vibrator pattern to be used as the default for notifications that do not specify vibration but vibrate anyway because the device is in vibrate mode. Loading @@ -2690,6 +2700,16 @@ <item>100</item> </integer-array> <!-- A vibration waveform for notifications that do not specify vibration but vibrate anyway, because the device is in vibrate mode. This value is a float array with values grouped as { targetAmplitude (within [0,1]), targetFrequency [-1,1], duration (in milliseconds) } This is only applied on devices with vibration frequency control. If the device doesn't support frequency control, then the vibration specified in config_notificationFallbackVibePattern is used instead. --> <array name="config_notificationFallbackVibeWaveform"> </array> <!-- Flag indicating if the speed up audio on mt call code should be executed --> <bool name="config_speed_up_audio_on_mt_calls">false</bool> Loading
core/res/res/values/symbols.xml +2 −0 Original line number Diff line number Diff line Loading @@ -1917,7 +1917,9 @@ <java-symbol type="array" name="config_locationExtraPackageNames" /> <java-symbol type="array" name="config_testLocationProviders" /> <java-symbol type="array" name="config_defaultNotificationVibePattern" /> <java-symbol type="array" name="config_defaultNotificationVibeWaveform" /> <java-symbol type="array" name="config_notificationFallbackVibePattern" /> <java-symbol type="array" name="config_notificationFallbackVibeWaveform" /> <java-symbol type="bool" name="config_enableServerNotificationEffectsForAutomotive" /> <java-symbol type="bool" name="config_useAttentionLight" /> <java-symbol type="bool" name="config_adaptive_sleep_available" /> Loading
services/core/java/com/android/server/notification/VibratorHelper.java +74 −24 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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); Loading @@ -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); } /** Loading @@ -82,6 +85,50 @@ public final class VibratorHelper { return null; } /** * Safely create a {@link VibrationEffect} from given waveform description. * * <p>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)}. * * <p>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}. * Loading @@ -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); } Loading @@ -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) { Loading
services/tests/uiservicestests/src/com/android/server/notification/VibratorHelperTest.java +12 −0 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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 Loading @@ -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 Loading