Merge "Use PWLE for waveforms and add ramp down to zero" into sc-dev

This commit is contained in:
TreeHugger Robot
2021-06-15 20:50:45 +00:00
committed by Android (Google) Code Review
11 changed files with 672 additions and 70 deletions

View File

@@ -3354,6 +3354,15 @@
(e.g. accessibility, alarms). This is mainly for Wear devices that don't have speakers. -->
<bool name="config_allowPriorityVibrationsInLowPowerMode">false</bool>
<!-- The duration (in milliseconds) that should be used to convert vibration ramps to a sequence
of fixed amplitude steps on devices without PWLE support. -->
<integer name="config_vibrationWaveformRampStepDuration">5</integer>
<!-- The duration (in milliseconds) that should be applied to waveform vibrations that ends in
non-zero amplitudes, . The waveform will
be played as a PWLE instead of on/off calls if this value is set. -->
<integer name="config_vibrationWaveformRampDownDuration">0</integer>
<!-- Number of retries Cell Data should attempt for a given error code before
restarting the modem.
Error codes not listed will not lead to modem restarts.

View File

@@ -2012,6 +2012,8 @@
<java-symbol type="integer" name="config_notificationServiceArchiveSize" />
<java-symbol type="integer" name="config_previousVibrationsDumpLimit" />
<java-symbol type="integer" name="config_defaultVibrationAmplitude" />
<java-symbol type="integer" name="config_vibrationWaveformRampStepDuration" />
<java-symbol type="integer" name="config_vibrationWaveformRampDownDuration" />
<java-symbol type="integer" name="config_radioScanningTimeout" />
<java-symbol type="integer" name="config_screenBrightnessSettingMinimum" />
<java-symbol type="integer" name="config_screenBrightnessSettingMaximum" />

View File

@@ -16,6 +16,7 @@
package com.android.server.vibrator;
import android.content.Context;
import android.os.VibrationEffect;
import android.os.VibratorInfo;
@@ -26,17 +27,16 @@ import java.util.List;
final class DeviceVibrationEffectAdapter
implements VibrationEffectAdapters.EffectAdapter<VibratorInfo> {
/** Duration of each step created to simulate a ramp segment. */
private static final int RAMP_STEP_DURATION_MILLIS = 5;
private final List<VibrationEffectAdapters.SegmentsAdapter<VibratorInfo>> mSegmentAdapters;
DeviceVibrationEffectAdapter() {
DeviceVibrationEffectAdapter(Context context) {
mSegmentAdapters = Arrays.asList(
// TODO(b/167947076): add filter that removes unsupported primitives
// TODO(b/167947076): add filter that replaces unsupported prebaked with fallback
new RampToStepAdapter(RAMP_STEP_DURATION_MILLIS),
new StepToRampAdapter(),
new RampToStepAdapter(context.getResources().getInteger(
com.android.internal.R.integer.config_vibrationWaveformRampStepDuration)),
new StepToRampAdapter(context.getResources().getInteger(
com.android.internal.R.integer.config_vibrationWaveformRampDownDuration)),
new ClippingAmplitudeAndFrequencyAdapter()
);
}

View File

@@ -29,7 +29,7 @@ import java.util.List;
/**
* Adapter that converts ramp segments that to a sequence of fixed step segments.
*
* <p>This leaves the list unchanged if the device have compose PWLE capability.
* <p>This leaves the list unchanged if the device has compose PWLE capability.
*/
final class RampToStepAdapter implements VibrationEffectAdapters.SegmentsAdapter<VibratorInfo> {

View File

@@ -27,39 +27,221 @@ import java.util.List;
/**
* Adapter that converts step segments that should be handled as PWLEs to ramp segments.
*
* <p>This leaves the list unchanged if the device do not have compose PWLE capability.
* <p>Each replaced {@link StepSegment} will be represented by a {@link RampSegment} with same
* start and end amplitudes/frequencies, which can then be converted to PWLE compositions. This
* adapter leaves the segments unchanged if the device doesn't have the PWLE composition capability.
*
* <p>This adapter also applies the ramp down duration config on devices with PWLE support. This
* prevents the device from ringing when it cannot handle abrupt changes between ON and OFF states.
* This will not change other types of abrupt amplitude changes in the original effect.
*
* <p>The effect overall duration is preserved by this transformation. Waveforms with ON/OFF
* segments are handled gracefully by the ramp down changes. Each OFF segment preceded by an ON
* segment will be shortened, and a ramp down will be added to the transition between ON and OFF.
* The ramps can be shorter than the configured duration in order to preserve the waveform timings,
* but they will still soften the ringing effect.
*/
final class StepToRampAdapter implements VibrationEffectAdapters.SegmentsAdapter<VibratorInfo> {
private final int mRampDownDuration;
StepToRampAdapter(int rampDownDuration) {
mRampDownDuration = rampDownDuration;
}
@Override
public int apply(List<VibrationEffectSegment> segments, int repeatIndex,
VibratorInfo info) {
if (!info.hasCapability(IVibrator.CAP_COMPOSE_PWLE_EFFECTS)) {
// The vibrator do not have PWLE capability, so keep the segments unchanged.
// The vibrator does not have PWLE capability, so keep the segments unchanged.
return repeatIndex;
}
convertStepsToRamps(segments);
int newRepeatIndex = addRampDownToZeroAmplitudeSegments(segments, repeatIndex);
newRepeatIndex = addRampDownToLoop(segments, newRepeatIndex);
return newRepeatIndex;
}
private void convertStepsToRamps(List<VibrationEffectSegment> segments) {
int segmentCount = segments.size();
if (mRampDownDuration > 0) {
// Convert all steps to ramps if the device requires ramp down.
for (int i = 0; i < segmentCount; i++) {
if (isStep(segments.get(i))) {
segments.set(i, apply((StepSegment) segments.get(i)));
}
}
return;
}
// Convert steps that require frequency control to ramps.
for (int i = 0; i < segmentCount; i++) {
VibrationEffectSegment segment = segments.get(i);
if ((segment instanceof StepSegment)
&& ((StepSegment) segment).getFrequency() != 0) {
if (isStep(segment) && ((StepSegment) segment).getFrequency() != 0) {
segments.set(i, apply((StepSegment) segment));
}
}
// Convert steps that are next to ramps to also become ramps, so they can be composed
// together in the same PWLE waveform.
for (int i = 1; i < segmentCount; i++) {
for (int i = 0; i < segmentCount; i++) {
if (segments.get(i) instanceof RampSegment) {
for (int j = i - 1; j >= 0 && (segments.get(j) instanceof StepSegment); j--) {
for (int j = i - 1; j >= 0 && isStep(segments.get(j)); j--) {
segments.set(j, apply((StepSegment) segments.get(j)));
}
for (int j = i + 1; j < segmentCount && isStep(segments.get(j)); j++) {
segments.set(j, apply((StepSegment) segments.get(j)));
}
}
}
}
/**
* This will add a ramp to zero as follows:
*
* <ol>
* <li>Remove the {@link VibrationEffectSegment} that starts and ends at zero amplitude
* and follows a segment that ends at non-zero amplitude;
* <li>Add a ramp down to zero starting at the previous segment end amplitude and frequency,
* with min between the removed segment duration and the configured ramp down duration;
* <li>Add a zero amplitude segment following the ramp with the remaining duration, if
* necessary;
* </ol>
*/
private int addRampDownToZeroAmplitudeSegments(List<VibrationEffectSegment> segments,
int repeatIndex) {
if (mRampDownDuration <= 0) {
// Nothing to do, no ramp down duration configured.
return repeatIndex;
}
int newRepeatIndex = repeatIndex;
int newSegmentCount = segments.size();
for (int i = 1; i < newSegmentCount; i++) {
if (!isOffRampSegment(segments.get(i))
|| !endsWithNonZeroAmplitude(segments.get(i - 1))) {
continue;
}
// We know the previous segment is a ramp that ends at non-zero amplitude.
float previousAmplitude = ((RampSegment) segments.get(i - 1)).getEndAmplitude();
float previousFrequency = ((RampSegment) segments.get(i - 1)).getEndFrequency();
RampSegment ramp = (RampSegment) segments.get(i);
if (ramp.getDuration() <= mRampDownDuration) {
// Replace the zero amplitude segment with a ramp down of same duration, to
// preserve waveform timings and still soften the transition to zero.
segments.set(i, createRampDown(previousAmplitude, previousFrequency,
ramp.getDuration()));
} else {
// Make the zero amplitude segment shorter, to preserve waveform timings, and add a
// ramp down to zero segment right before it.
segments.set(i, updateDuration(ramp, ramp.getDuration() - mRampDownDuration));
segments.add(i, createRampDown(previousAmplitude, previousFrequency,
mRampDownDuration));
if (repeatIndex > i) {
newRepeatIndex++;
}
i++;
newSegmentCount++;
}
}
return newRepeatIndex;
}
/**
* This will add a ramp to zero at the repeating index of the given effect, if set, only if
* the last segment ends at a non-zero amplitude and the repeating segment starts and ends at
* zero amplitude. The update is described as:
*
* <ol>
* <li>Add a ramp down to zero following the last segment, with the min between the
* removed segment duration and the configured ramp down duration;
* <li>Skip the zero-amplitude segment by incrementing the repeat index, splitting it if
* necessary to skip the correct amount;
* </ol>
*/
private int addRampDownToLoop(List<VibrationEffectSegment> segments, int repeatIndex) {
if (repeatIndex < 0) {
// Non-repeating compositions should remain unchanged so duration will be preserved.
return repeatIndex;
}
int segmentCount = segments.size();
if (mRampDownDuration <= 0 || !endsWithNonZeroAmplitude(segments.get(segmentCount - 1))) {
// Nothing to do, no ramp down duration configured or composition already ends at zero.
return repeatIndex;
}
// We know the last segment is a ramp that ends at non-zero amplitude.
RampSegment lastRamp = (RampSegment) segments.get(segmentCount - 1);
float previousAmplitude = lastRamp.getEndAmplitude();
float previousFrequency = lastRamp.getEndFrequency();
if (isOffRampSegment(segments.get(repeatIndex))) {
// Repeating from a non-zero to a zero amplitude segment, we know the next segment is a
// ramp with zero amplitudes.
RampSegment nextRamp = (RampSegment) segments.get(repeatIndex);
if (nextRamp.getDuration() <= mRampDownDuration) {
// Skip the zero amplitude segment and append a ramp down of same duration to the
// end of the composition, to preserve waveform timings and still soften the
// transition to zero.
// This will update the waveform as follows:
// R R+1
// | ____ | ____
// _|_/ => __|/ \
segments.add(createRampDown(previousAmplitude, previousFrequency,
nextRamp.getDuration()));
repeatIndex++;
} else {
// Append a ramp down to the end of the composition, split the zero amplitude
// segment and start repeating from the second half, to preserve waveform timings.
// This will update the waveform as follows:
// R R+1
// | ____ | ____
// _|__/ => __|_/ \
segments.add(createRampDown(previousAmplitude, previousFrequency,
mRampDownDuration));
segments.set(repeatIndex, updateDuration(nextRamp,
nextRamp.getDuration() - mRampDownDuration));
segments.add(repeatIndex, updateDuration(nextRamp, mRampDownDuration));
repeatIndex++;
}
}
return repeatIndex;
}
private RampSegment apply(StepSegment segment) {
private static RampSegment apply(StepSegment segment) {
return new RampSegment(segment.getAmplitude(), segment.getAmplitude(),
segment.getFrequency(), segment.getFrequency(), (int) segment.getDuration());
}
private static RampSegment createRampDown(float amplitude, float frequency, long duration) {
return new RampSegment(amplitude, /* endAmplitude= */ 0, frequency, frequency,
(int) duration);
}
private static RampSegment updateDuration(RampSegment ramp, long newDuration) {
return new RampSegment(ramp.getStartAmplitude(), ramp.getEndAmplitude(),
ramp.getStartFrequency(), ramp.getEndFrequency(), (int) newDuration);
}
private static boolean isStep(VibrationEffectSegment segment) {
return segment instanceof StepSegment;
}
/** Returns true if the segment is a ramp that starts and ends at zero amplitude. */
private static boolean isOffRampSegment(VibrationEffectSegment segment) {
if (segment instanceof RampSegment) {
RampSegment ramp = (RampSegment) segment;
return ramp.getStartAmplitude() == 0 && ramp.getEndAmplitude() == 0;
}
return false;
}
private static boolean endsWithNonZeroAmplitude(VibrationEffectSegment segment) {
if (segment instanceof RampSegment) {
return ((RampSegment) segment).getEndAmplitude() != 0;
}
return false;
}
}

View File

@@ -95,8 +95,7 @@ final class VibrationThread extends Thread implements IBinder.DeathRecipient {
private final WorkSource mWorkSource = new WorkSource();
private final PowerManager.WakeLock mWakeLock;
private final IBatteryStats mBatteryStatsService;
private final DeviceVibrationEffectAdapter mDeviceEffectAdapter =
new DeviceVibrationEffectAdapter();
private final DeviceVibrationEffectAdapter mDeviceEffectAdapter;
private final Vibration mVibration;
private final VibrationCallbacks mCallbacks;
private final SparseArray<VibratorController> mVibrators = new SparseArray<>();
@@ -104,10 +103,11 @@ final class VibrationThread extends Thread implements IBinder.DeathRecipient {
private volatile boolean mForceStop;
VibrationThread(Vibration vib, SparseArray<VibratorController> availableVibrators,
PowerManager.WakeLock wakeLock, IBatteryStats batteryStatsService,
VibrationCallbacks callbacks) {
VibrationThread(Vibration vib, DeviceVibrationEffectAdapter effectAdapter,
SparseArray<VibratorController> availableVibrators, PowerManager.WakeLock wakeLock,
IBatteryStats batteryStatsService, VibrationCallbacks callbacks) {
mVibration = vib;
mDeviceEffectAdapter = effectAdapter;
mCallbacks = callbacks;
mWakeLock = wakeLock;
mWorkSource.set(vib.uid);

View File

@@ -132,6 +132,7 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
private final VibrationSettings mVibrationSettings;
private final VibrationScaler mVibrationScaler;
private final InputDeviceDelegate mInputDeviceDelegate;
private final DeviceVibrationEffectAdapter mDeviceVibrationEffectAdapter;
private BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {
@Override
@@ -175,6 +176,7 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
mVibrationSettings = new VibrationSettings(mContext, mHandler);
mVibrationScaler = new VibrationScaler(mContext, mVibrationSettings);
mInputDeviceDelegate = new InputDeviceDelegate(mContext, mHandler);
mDeviceVibrationEffectAdapter = new DeviceVibrationEffectAdapter(mContext);
VibrationCompleteListener listener = new VibrationCompleteListener(this);
mNativeWrapper = injector.getNativeWrapper();
@@ -512,8 +514,8 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
return Vibration.Status.FORWARDED_TO_INPUT_DEVICES;
}
VibrationThread vibThread = new VibrationThread(vib, mVibrators, mWakeLock,
mBatteryStatsService, mVibrationCallbacks);
VibrationThread vibThread = new VibrationThread(vib, mDeviceVibrationEffectAdapter,
mVibrators, mWakeLock, mBatteryStatsService, mVibrationCallbacks);
if (mCurrentVibration == null) {
return startVibrationThreadLocked(vibThread);

View File

@@ -17,6 +17,7 @@
package com.android.server.vibrator;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import android.hardware.vibrator.IVibrator;
import android.os.VibrationEffect;
@@ -25,8 +26,11 @@ import android.os.vibrator.PrebakedSegment;
import android.os.vibrator.PrimitiveSegment;
import android.os.vibrator.RampSegment;
import android.os.vibrator.StepSegment;
import android.os.vibrator.VibrationEffectSegment;
import android.platform.test.annotations.Presubmit;
import androidx.test.InstrumentationRegistry;
import org.junit.Before;
import org.junit.Test;
@@ -58,7 +62,7 @@ public class DeviceVibrationEffectAdapterTest {
@Before
public void setUp() throws Exception {
mAdapter = new DeviceVibrationEffectAdapter();
mAdapter = new DeviceVibrationEffectAdapter(InstrumentationRegistry.getContext());
}
@Test
@@ -84,28 +88,19 @@ public class DeviceVibrationEffectAdapterTest {
new RampSegment(/* startAmplitude= */ 1, /* endAmplitude= */ 0.2f,
/* startFrequency= */ -4, /* endFrequency= */ 2, /* duration= */ 10),
new RampSegment(/* startAmplitude= */ 0.8f, /* endAmplitude= */ 0.2f,
/* startFrequency= */ 0, /* endFrequency= */ 0, /* duration= */ 11),
/* startFrequency= */ 0, /* endFrequency= */ 0, /* duration= */ 100),
new RampSegment(/* startAmplitude= */ 0.65f, /* endAmplitude= */ 0.65f,
/* startFrequency= */ 0, /* endFrequency= */ 1, /* duration= */ 200)),
/* startFrequency= */ 0, /* endFrequency= */ 1, /* duration= */ 1000)),
/* repeatIndex= */ 3);
VibrationEffect.Composed expected = new VibrationEffect.Composed(Arrays.asList(
new StepSegment(/* amplitude= */ 0, Float.NaN, /* duration= */ 10),
new StepSegment(/* amplitude= */ 0.5f, Float.NaN, /* duration= */ 100),
// 10ms ramp becomes 2 steps
new StepSegment(/* amplitude= */ 1, Float.NaN, /* duration= */ 5),
new StepSegment(/* amplitude= */ 0.2f, Float.NaN, /* duration= */ 5),
// 11ms ramp becomes 3 steps
new StepSegment(/* amplitude= */ 0.8f, Float.NaN, /* duration= */ 5),
new StepSegment(/* amplitude= */ 0.6f, Float.NaN, /* duration= */ 5),
new StepSegment(/* amplitude= */ 0.2f, Float.NaN, /* duration= */ 1),
// 200ms ramp with same amplitude becomes a single step
new StepSegment(/* amplitude= */ 0.65f, Float.NaN, /* duration= */ 200)),
// Repeat index fixed after intermediate steps added
/* repeatIndex= */ 4);
VibrationEffect.Composed adaptedEffect = (VibrationEffect.Composed) mAdapter.apply(effect,
createVibratorInfo(EMPTY_FREQUENCY_MAPPING));
assertTrue(adaptedEffect.getSegments().size() > effect.getSegments().size());
assertTrue(adaptedEffect.getRepeatIndex() >= effect.getRepeatIndex());
VibratorInfo info = createVibratorInfo(EMPTY_FREQUENCY_MAPPING);
assertEquals(expected, mAdapter.apply(effect, info));
for (VibrationEffectSegment adaptedSegment : adaptedEffect.getSegments()) {
assertTrue(adaptedSegment instanceof StepSegment);
}
}
@Test
@@ -135,33 +130,6 @@ public class DeviceVibrationEffectAdapterTest {
assertEquals(expected, mAdapter.apply(effect, info));
}
@Test
public void testStepAndRampSegments_withPwleCapabilityAndNoFrequency_keepsOriginalSteps() {
VibrationEffect.Composed effect = new VibrationEffect.Composed(Arrays.asList(
new StepSegment(/* amplitude= */ 0, /* frequency= */ 0, /* duration= */ 10),
new StepSegment(/* amplitude= */ 0.5f, /* frequency= */ 0, /* duration= */ 100),
new PrimitiveSegment(VibrationEffect.Composition.PRIMITIVE_TICK, 1, 10),
new RampSegment(/* startAmplitude= */ 1, /* endAmplitude= */ 1,
/* startFrequency= */ -4, /* endFrequency= */ 2, /* duration= */ 50),
new RampSegment(/* startAmplitude= */ 0.8f, /* endAmplitude= */ 0.2f,
/* startFrequency= */ 10, /* endFrequency= */ -5, /* duration= */ 20)),
/* repeatIndex= */ 2);
VibrationEffect.Composed expected = new VibrationEffect.Composed(Arrays.asList(
new StepSegment(/* amplitude= */ 0, /* frequency= */ 150, /* duration= */ 10),
new StepSegment(/* amplitude= */ 0.5f, /* frequency= */ 150, /* duration= */ 100),
new PrimitiveSegment(VibrationEffect.Composition.PRIMITIVE_TICK, 1, 10),
new RampSegment(/* startAmplitude= */ 0.1f, /* endAmplitude= */ 0.8f,
/* startFrequency= */ 50, /* endFrequency= */ 200, /* duration= */ 50),
new RampSegment(/* startAmplitude= */ 0.8f, /* endAmplitude= */ 0.1f,
/* startFrequency= */ 200, /* endFrequency= */ 50, /* duration= */ 20)),
/* repeatIndex= */ 2);
VibratorInfo info = createVibratorInfo(TEST_FREQUENCY_MAPPING,
IVibrator.CAP_COMPOSE_PWLE_EFFECTS);
assertEquals(expected, mAdapter.apply(effect, info));
}
@Test
public void testStepAndRampSegments_withEmptyFreqMapping_returnsSameAmplitudesAndZeroFreq() {
VibrationEffect.Composed effect = new VibrationEffect.Composed(Arrays.asList(

View File

@@ -0,0 +1,122 @@
/*
* Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.server.vibrator;
import static org.junit.Assert.assertEquals;
import android.hardware.vibrator.IVibrator;
import android.os.VibrationEffect;
import android.os.VibratorInfo;
import android.os.vibrator.PrebakedSegment;
import android.os.vibrator.PrimitiveSegment;
import android.os.vibrator.RampSegment;
import android.os.vibrator.StepSegment;
import android.os.vibrator.VibrationEffectSegment;
import android.platform.test.annotations.Presubmit;
import org.junit.Before;
import org.junit.Test;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.IntStream;
/**
* Tests for {@link RampToStepAdapter}.
*
* Build/Install/Run:
* atest FrameworksServicesTests:RampToStepAdapterTest
*/
@Presubmit
public class RampToStepAdapterTest {
private static final int TEST_STEP_DURATION = 5;
private RampToStepAdapter mAdapter;
@Before
public void setUp() throws Exception {
mAdapter = new RampToStepAdapter(TEST_STEP_DURATION);
}
@Test
public void testStepAndPrebakedAndPrimitiveSegments_keepsListUnchanged() {
List<VibrationEffectSegment> segments = new ArrayList<>(Arrays.asList(
new StepSegment(/* amplitude= */ 0, /* frequency= */ 1, /* duration= */ 10),
new PrebakedSegment(
VibrationEffect.EFFECT_CLICK, false, VibrationEffect.EFFECT_STRENGTH_LIGHT),
new PrimitiveSegment(VibrationEffect.Composition.PRIMITIVE_TICK, 1, 10)));
List<VibrationEffectSegment> originalSegments = new ArrayList<>(segments);
assertEquals(-1, mAdapter.apply(segments, -1, createVibratorInfo()));
assertEquals(1, mAdapter.apply(segments, 1, createVibratorInfo()));
assertEquals(originalSegments, segments);
}
@Test
public void testRampSegments_withPwleCapability_keepsListUnchanged() {
List<VibrationEffectSegment> segments = new ArrayList<>(Arrays.asList(
new StepSegment(/* amplitude= */ 0.5f, /* frequency= */ 0, /* duration= */ 100),
new RampSegment(/* startAmplitude= */ 0.8f, /* endAmplitude= */ 0.2f,
/* startFrequency= */ 10, /* endFrequency= */ -5, /* duration= */ 20)));
List<VibrationEffectSegment> originalSegments = new ArrayList<>(segments);
VibratorInfo vibratorInfo = createVibratorInfo(IVibrator.CAP_COMPOSE_PWLE_EFFECTS);
assertEquals(-1, mAdapter.apply(segments, -1, vibratorInfo));
assertEquals(0, mAdapter.apply(segments, 0, vibratorInfo));
assertEquals(originalSegments, segments);
}
@Test
public void testRampSegments_withoutPwleCapability_convertsRampsToSteps() {
List<VibrationEffectSegment> segments = new ArrayList<>(Arrays.asList(
new StepSegment(/* amplitude= */ 0, /* frequency= */ 1, /* duration= */ 10),
new StepSegment(/* amplitude= */ 0.5f, /* frequency= */ 0, /* duration= */ 100),
new RampSegment(/* startAmplitude= */ 1, /* endAmplitude= */ 0.2f,
/* startFrequency= */ -4, /* endFrequency= */ 2, /* duration= */ 10),
new RampSegment(/* startAmplitude= */ 0.8f, /* endAmplitude= */ 0.2f,
/* startFrequency= */ -3, /* endFrequency= */ 0, /* duration= */ 11),
new RampSegment(/* startAmplitude= */ 0.65f, /* endAmplitude= */ 0.65f,
/* startFrequency= */ 0, /* endFrequency= */ 1, /* duration= */ 200)));
List<VibrationEffectSegment> expectedSegments = Arrays.asList(
new StepSegment(/* amplitude= */ 0, /* frequency= */ 1, /* duration= */ 10),
new StepSegment(/* amplitude= */ 0.5f, /* frequency= */ 0, /* duration= */ 100),
// 10ms ramp becomes 2 steps
new StepSegment(/* amplitude= */ 1, /* frequency= */ -4, /* duration= */ 5),
new StepSegment(/* amplitude= */ 0.2f, /* frequency= */ 2, /* duration= */ 5),
// 11ms ramp becomes 3 steps
new StepSegment(/* amplitude= */ 0.8f, /* frequency= */ -3, /* duration= */ 5),
new StepSegment(/* amplitude= */ 0.6f, /* frequency= */ -2, /* duration= */ 5),
new StepSegment(/* amplitude= */ 0.2f, /* frequency= */ 0, /* duration= */ 1),
// 200ms ramp with same amplitude becomes a single step
new StepSegment(/* amplitude= */ 0.65f, /* frequency= */ 0, /* duration= */ 200));
// Repeat index fixed after intermediate steps added
assertEquals(4, mAdapter.apply(segments, 3, createVibratorInfo()));
assertEquals(expectedSegments, segments);
}
private static VibratorInfo createVibratorInfo(int... capabilities) {
return new VibratorInfo.Builder(0)
.setCapabilities(IntStream.of(capabilities).reduce((a, b) -> a | b).orElse(0))
.build();
}
}

View File

@@ -0,0 +1,312 @@
/*
* Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.server.vibrator;
import static org.junit.Assert.assertEquals;
import android.hardware.vibrator.IVibrator;
import android.os.VibrationEffect;
import android.os.VibratorInfo;
import android.os.vibrator.PrebakedSegment;
import android.os.vibrator.PrimitiveSegment;
import android.os.vibrator.RampSegment;
import android.os.vibrator.StepSegment;
import android.os.vibrator.VibrationEffectSegment;
import android.platform.test.annotations.Presubmit;
import org.junit.Before;
import org.junit.Test;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.IntStream;
/**
* Tests for {@link StepToRampAdapter}.
*
* Build/Install/Run:
* atest FrameworksServicesTests:StepToRampAdapterTest
*/
@Presubmit
public class StepToRampAdapterTest {
private StepToRampAdapter mAdapter;
@Before
public void setUp() throws Exception {
mAdapter = new StepToRampAdapter(/* rampDownDuration= */ 0);
}
@Test
public void testRampAndPrebakedAndPrimitiveSegments_returnsOriginalSegments() {
List<VibrationEffectSegment> segments = new ArrayList<>(Arrays.asList(
new RampSegment(/* startAmplitude= */ 1, /* endAmplitude= */ 0.2f,
/* startFrequency= */ -4, /* endFrequency= */ 2, /* duration= */ 10),
new PrebakedSegment(
VibrationEffect.EFFECT_CLICK, false, VibrationEffect.EFFECT_STRENGTH_LIGHT),
new PrimitiveSegment(VibrationEffect.Composition.PRIMITIVE_TICK, 1, 10)));
List<VibrationEffectSegment> originalSegments = new ArrayList<>(segments);
assertEquals(-1, mAdapter.apply(segments, -1, createVibratorInfo()));
assertEquals(1, mAdapter.apply(segments, 1, createVibratorInfo()));
assertEquals(originalSegments, segments);
}
@Test
public void testStepAndRampSegments_withoutPwleCapability_keepsListUnchanged() {
mAdapter = new StepToRampAdapter(50);
List<VibrationEffectSegment> segments = new ArrayList<>(Arrays.asList(
new StepSegment(/* amplitude= */ 0, /* frequency= */ 1, /* duration= */ 10),
new RampSegment(/* startAmplitude= */ 0.8f, /* endAmplitude= */ 0.2f,
/* startFrequency= */ 10, /* endFrequency= */ -5, /* duration= */ 20)));
List<VibrationEffectSegment> originalSegments = new ArrayList<>(segments);
assertEquals(-1, mAdapter.apply(segments, -1, createVibratorInfo()));
assertEquals(0, mAdapter.apply(segments, 0, createVibratorInfo()));
assertEquals(originalSegments, segments);
}
@Test
public void testStepAndRampSegments_withPwleCapabilityAndNoFrequency_keepsOriginalSteps() {
List<VibrationEffectSegment> segments = new ArrayList<>(Arrays.asList(
new StepSegment(/* amplitude= */ 0, /* frequency= */ 0, /* duration= */ 10),
new StepSegment(/* amplitude= */ 0.5f, /* frequency= */ 0, /* duration= */ 100),
new PrimitiveSegment(VibrationEffect.Composition.PRIMITIVE_TICK, 1, 10),
new RampSegment(/* startAmplitude= */ 1, /* endAmplitude= */ 1,
/* startFrequency= */ -4, /* endFrequency= */ 2, /* duration= */ 50),
new RampSegment(/* startAmplitude= */ 0.8f, /* endAmplitude= */ 0.2f,
/* startFrequency= */ 10, /* endFrequency= */ -5, /* duration= */ 20)));
List<VibrationEffectSegment> originalSegments = new ArrayList<>(segments);
VibratorInfo vibratorInfo = createVibratorInfo(IVibrator.CAP_COMPOSE_PWLE_EFFECTS);
assertEquals(-1, mAdapter.apply(segments, -1, vibratorInfo));
assertEquals(3, mAdapter.apply(segments, 3, vibratorInfo));
assertEquals(originalSegments, segments);
}
@Test
public void testStepAndRampSegments_withPwleCapabilityAndStepNextToRamp_convertsStepsToRamps() {
List<VibrationEffectSegment> segments = new ArrayList<>(Arrays.asList(
new StepSegment(/* amplitude= */ 0, /* frequency= */ 1, /* duration= */ 10),
new StepSegment(/* amplitude= */ 0.5f, /* frequency= */ 0, /* duration= */ 100),
new RampSegment(/* startAmplitude= */ 1, /* endAmplitude= */ 1,
/* startFrequency= */ -4, /* endFrequency= */ 2, /* duration= */ 50),
new RampSegment(/* startAmplitude= */ 0.8f, /* endAmplitude= */ 0.2f,
/* startFrequency= */ 10, /* endFrequency= */ -5, /* duration= */ 20),
new StepSegment(/* amplitude= */ 0.8f, /* frequency= */ -1, /* duration= */ 60)));
List<VibrationEffectSegment> expectedSegments = Arrays.asList(
new RampSegment(/* startAmplitude= */ 0, /* endAmplitude*/ 0,
/* startFrequency= */ 1, /* endFrequency= */ 1, /* duration= */ 10),
new RampSegment(/* startAmplitude= */ 0.5f, /* endAmplitude= */ 0.5f,
/* startFrequency= */ 0, /* endFrequency= */ 0, /* duration= */ 100),
new RampSegment(/* startAmplitude= */ 1, /* endAmplitude= */ 1,
/* startFrequency= */ -4, /* endFrequency= */ 2, /* duration= */ 50),
new RampSegment(/* startAmplitude= */ 0.8f, /* endAmplitude= */ 0.2f,
/* startFrequency= */ 10, /* endFrequency= */ -5, /* duration= */ 20),
new RampSegment(/* startAmplitude= */ 0.8f, /* endAmplitude= */ 0.8f,
/* startFrequency= */ -1, /* endFrequency= */ -1, /* duration= */ 60));
VibratorInfo vibratorInfo = createVibratorInfo(IVibrator.CAP_COMPOSE_PWLE_EFFECTS);
assertEquals(-1, mAdapter.apply(segments, -1, vibratorInfo));
assertEquals(2, mAdapter.apply(segments, 2, vibratorInfo));
assertEquals(expectedSegments, segments);
}
@Test
public void testStepSegments_withPwleCapabilityAndFrequency_convertsStepsToRamps() {
List<VibrationEffectSegment> segments = new ArrayList<>(Arrays.asList(
new StepSegment(/* amplitude= */ 0, /* frequency= */ -1, /* duration= */ 10),
new StepSegment(/* amplitude= */ 0.5f, /* frequency= */ 1, /* duration= */ 100)));
List<VibrationEffectSegment> expectedSegments = Arrays.asList(
new RampSegment(/* startAmplitude= */ 0, /* endAmplitude*/ 0,
/* startFrequency= */ -1, /* endFrequency= */ -1, /* duration= */ 10),
new RampSegment(/* startAmplitude= */ 0.5f, /* endAmplitude= */ 0.5f,
/* startFrequency= */ 1, /* endFrequency= */ 1, /* duration= */ 100));
VibratorInfo vibratorInfo = createVibratorInfo(IVibrator.CAP_COMPOSE_PWLE_EFFECTS);
assertEquals(-1, mAdapter.apply(segments, -1, vibratorInfo));
assertEquals(0, mAdapter.apply(segments, 0, vibratorInfo));
assertEquals(expectedSegments, segments);
}
@Test
public void testStepSegments_withRampDownEndingAtNonZero_noRampDownAdded() {
int rampDownDuration = 50;
mAdapter = new StepToRampAdapter(rampDownDuration);
List<VibrationEffectSegment> segments = new ArrayList<>(Arrays.asList(
new StepSegment(/* amplitude= */ 0.5f, /* frequency= */ 0, /* duration= */ 10),
new StepSegment(/* amplitude= */ 0.8f, /* frequency= */ 1, /* duration= */ 100)));
List<VibrationEffectSegment> expectedSegments = Arrays.asList(
new RampSegment(/* startAmplitude= */ 0.5f, /* endAmplitude*/ 0.5f,
/* startFrequency= */ 0, /* endFrequency= */ 0, /* duration= */ 10),
new RampSegment(/* startAmplitude= */ 0.8f, /* endAmplitude= */ 0.8f,
/* startFrequency= */ 1, /* endFrequency= */ 1, /* duration= */ 100));
VibratorInfo vibratorInfo = createVibratorInfo(IVibrator.CAP_COMPOSE_PWLE_EFFECTS);
assertEquals(-1, mAdapter.apply(segments, -1, vibratorInfo));
assertEquals(expectedSegments, segments);
}
@Test
public void testStepSegments_withRampDownAndShortZeroSegment_replaceWithRampDown() {
mAdapter = new StepToRampAdapter(50);
List<VibrationEffectSegment> segments = new ArrayList<>(Arrays.asList(
new StepSegment(/* amplitude= */ 0.5f, /* frequency= */ -1, /* duration= */ 10),
new StepSegment(/* amplitude= */ 0, /* frequency= */ 0, /* duration= */ 20),
new StepSegment(/* amplitude= */ 1, /* frequency= */ 1, /* duration= */ 30)));
List<VibrationEffectSegment> expectedSegments = Arrays.asList(
new RampSegment(/* startAmplitude= */ 0.5f, /* endAmplitude*/ 0.5f,
/* startFrequency= */ -1, /* endFrequency= */ -1, /* duration= */ 10),
new RampSegment(/* startAmplitude= */ 0.5f, /* endAmplitude= */ 0,
/* startFrequency= */ -1, /* endFrequency= */ -1, /* duration= */ 20),
new RampSegment(/* startAmplitude= */ 1, /* endAmplitude= */ 1,
/* startFrequency= */ 1, /* endFrequency= */ 1, /* duration= */ 30));
VibratorInfo vibratorInfo = createVibratorInfo(IVibrator.CAP_COMPOSE_PWLE_EFFECTS);
assertEquals(2, mAdapter.apply(segments, 2, vibratorInfo));
assertEquals(expectedSegments, segments);
}
@Test
public void testStepSegments_withRampDownAndLongZeroSegment_splitAndAddRampDown() {
mAdapter = new StepToRampAdapter(50);
List<VibrationEffectSegment> segments = new ArrayList<>(Arrays.asList(
new StepSegment(/* amplitude= */ 0.5f, /* frequency= */ -1, /* duration= */ 10),
new StepSegment(/* amplitude= */ 0, /* frequency= */ 1, /* duration= */ 150),
new StepSegment(/* amplitude= */ 1, /* frequency= */ 1, /* duration= */ 30)));
List<VibrationEffectSegment> expectedSegments = Arrays.asList(
new RampSegment(/* startAmplitude= */ 0.5f, /* endAmplitude*/ 0.5f,
/* startFrequency= */ -1, /* endFrequency= */ -1, /* duration= */ 10),
new RampSegment(/* startAmplitude= */ 0.5f, /* endAmplitude= */ 0,
/* startFrequency= */ -1, /* endFrequency= */ -1, /* duration= */ 50),
new RampSegment(/* startAmplitude= */ 0, /* endAmplitude= */ 0,
/* startFrequency= */ 1, /* endFrequency= */ 1, /* duration= */ 100),
new RampSegment(/* startAmplitude= */ 1, /* endAmplitude= */ 1,
/* startFrequency= */ 1, /* endFrequency= */ 1, /* duration= */ 30));
VibratorInfo vibratorInfo = createVibratorInfo(IVibrator.CAP_COMPOSE_PWLE_EFFECTS);
// Repeat index fixed after intermediate steps added
assertEquals(3, mAdapter.apply(segments, 2, vibratorInfo));
assertEquals(expectedSegments, segments);
}
@Test
public void testStepSegments_withRampDownAndNoZeroSegment_noRampDownAdded() {
mAdapter = new StepToRampAdapter(50);
List<VibrationEffectSegment> segments = new ArrayList<>(Arrays.asList(
new StepSegment(/* amplitude= */ 0.5f, /* frequency= */ -1, /* duration= */ 10),
new StepSegment(/* amplitude= */ 1, /* frequency= */ 1, /* duration= */ 30),
new PrimitiveSegment(VibrationEffect.Composition.PRIMITIVE_TICK, 1, 10)));
List<VibrationEffectSegment> expectedSegments = Arrays.asList(
new RampSegment(/* startAmplitude= */ 0.5f, /* endAmplitude*/ 0.5f,
/* startFrequency= */ -1, /* endFrequency= */ -1, /* duration= */ 10),
new RampSegment(/* startAmplitude= */ 1, /* endAmplitude= */ 1,
/* startFrequency= */ 1, /* endFrequency= */ 1, /* duration= */ 30),
new PrimitiveSegment(VibrationEffect.Composition.PRIMITIVE_TICK, 1, 10));
VibratorInfo vibratorInfo = createVibratorInfo(IVibrator.CAP_COMPOSE_PWLE_EFFECTS);
assertEquals(-1, mAdapter.apply(segments, -1, vibratorInfo));
assertEquals(expectedSegments, segments);
}
@Test
public void testStepSegments_withRampDownAndRepeatToNonZeroSegment_noRampDownAdded() {
mAdapter = new StepToRampAdapter(50);
List<VibrationEffectSegment> segments = new ArrayList<>(Arrays.asList(
new StepSegment(/* amplitude= */ 0.5f, /* frequency= */ -1, /* duration= */ 10),
new StepSegment(/* amplitude= */ 1, /* frequency= */ 1, /* duration= */ 30)));
List<VibrationEffectSegment> expectedSegments = Arrays.asList(
new RampSegment(/* startAmplitude= */ 0.5f, /* endAmplitude*/ 0.5f,
/* startFrequency= */ -1, /* endFrequency= */ -1, /* duration= */ 10),
new RampSegment(/* startAmplitude= */ 1, /* endAmplitude= */ 1,
/* startFrequency= */ 1, /* endFrequency= */ 1, /* duration= */ 30));
VibratorInfo vibratorInfo = createVibratorInfo(IVibrator.CAP_COMPOSE_PWLE_EFFECTS);
assertEquals(0, mAdapter.apply(segments, 0, vibratorInfo));
assertEquals(expectedSegments, segments);
}
@Test
public void testStepSegments_withRampDownAndRepeatToShortZeroSegment_skipAndAppendRampDown() {
mAdapter = new StepToRampAdapter(50);
List<VibrationEffectSegment> segments = new ArrayList<>(Arrays.asList(
new StepSegment(/* amplitude= */ 0, /* frequency= */ 1, /* duration= */ 20),
new StepSegment(/* amplitude= */ 1, /* frequency= */ 1, /* duration= */ 30)));
List<VibrationEffectSegment> expectedSegments = Arrays.asList(
new RampSegment(/* startAmplitude= */ 0, /* endAmplitude*/ 0,
/* startFrequency= */ 1, /* endFrequency= */ 1, /* duration= */ 20),
new RampSegment(/* startAmplitude= */ 1, /* endAmplitude= */ 1,
/* startFrequency= */ 1, /* endFrequency= */ 1, /* duration= */ 30),
new RampSegment(/* startAmplitude= */ 1, /* endAmplitude= */ 0,
/* startFrequency= */ 1, /* endFrequency= */ 1, /* duration= */ 20));
VibratorInfo vibratorInfo = createVibratorInfo(IVibrator.CAP_COMPOSE_PWLE_EFFECTS);
// Shift repeat index to the right to use append instead of zero segment.
assertEquals(1, mAdapter.apply(segments, 0, vibratorInfo));
assertEquals(expectedSegments, segments);
}
@Test
public void testStepSegments_withRampDownAndRepeatToLongZeroSegment_splitAndAppendRampDown() {
mAdapter = new StepToRampAdapter(50);
List<VibrationEffectSegment> segments = new ArrayList<>(Arrays.asList(
new StepSegment(/* amplitude= */ 0, /* frequency= */ 1, /* duration= */ 120),
new StepSegment(/* amplitude= */ 1, /* frequency= */ 1, /* duration= */ 30)));
List<VibrationEffectSegment> expectedSegments = Arrays.asList(
// Split long zero segment to skip part of it.
new RampSegment(/* startAmplitude= */ 0, /* endAmplitude*/ 0,
/* startFrequency= */ 1, /* endFrequency= */ 1, /* duration= */ 50),
new RampSegment(/* startAmplitude= */ 0, /* endAmplitude*/ 0,
/* startFrequency= */ 1, /* endFrequency= */ 1, /* duration= */ 70),
new RampSegment(/* startAmplitude= */ 1, /* endAmplitude= */ 1,
/* startFrequency= */ 1, /* endFrequency= */ 1, /* duration= */ 30),
new RampSegment(/* startAmplitude= */ 1, /* endAmplitude= */ 0,
/* startFrequency= */ 1, /* endFrequency= */ 1, /* duration= */ 50));
VibratorInfo vibratorInfo = createVibratorInfo(IVibrator.CAP_COMPOSE_PWLE_EFFECTS);
// Shift repeat index to the right to use append with part of the zero segment.
assertEquals(1, mAdapter.apply(segments, 0, vibratorInfo));
assertEquals(expectedSegments, segments);
}
private static VibratorInfo createVibratorInfo(int... capabilities) {
return new VibratorInfo.Builder(0)
.setCapabilities(IntStream.of(capabilities).reduce((a, b) -> a | b).orElse(0))
.build();
}
}

View File

@@ -31,6 +31,7 @@ import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.content.Context;
import android.hardware.vibrator.Braking;
import android.hardware.vibrator.IVibrator;
import android.hardware.vibrator.IVibratorManager;
@@ -98,13 +99,17 @@ public class VibrationThreadTest {
private IBatteryStats mIBatteryStatsMock;
private final Map<Integer, FakeVibratorControllerProvider> mVibratorProviders = new HashMap<>();
private DeviceVibrationEffectAdapter mEffectAdapter;
private PowerManager.WakeLock mWakeLock;
private TestLooper mTestLooper;
@Before
public void setUp() throws Exception {
mTestLooper = new TestLooper();
mWakeLock = InstrumentationRegistry.getContext().getSystemService(
Context context = InstrumentationRegistry.getContext();
mEffectAdapter = new DeviceVibrationEffectAdapter(context);
mWakeLock = context.getSystemService(
PowerManager.class).newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "*vibrator*");
mockVibrators(VIBRATOR_ID);
@@ -985,8 +990,8 @@ public class VibrationThreadTest {
}
private VibrationThread startThreadAndDispatcher(Vibration vib) {
VibrationThread thread = new VibrationThread(vib, createVibratorControllers(), mWakeLock,
mIBatteryStatsMock, mThreadCallbacks);
VibrationThread thread = new VibrationThread(vib, mEffectAdapter,
createVibratorControllers(), mWakeLock, mIBatteryStatsMock, mThreadCallbacks);
doAnswer(answer -> {
thread.vibratorComplete(answer.getArgument(0));
return null;