Merge "Split vibration effect adapters into separate files" into sc-dev

This commit is contained in:
TreeHugger Robot
2021-06-02 15:42:50 +00:00
committed by Android (Google) Code Review
7 changed files with 343 additions and 237 deletions

View File

@@ -0,0 +1,80 @@
/*
* 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 android.os.VibratorInfo;
import android.os.vibrator.RampSegment;
import android.os.vibrator.StepSegment;
import android.os.vibrator.VibrationEffectSegment;
import android.util.MathUtils;
import java.util.List;
/**
* Adapter that clips frequency values to {@link VibratorInfo#getFrequencyRange()} and
* amplitude values to respective {@link VibratorInfo#getMaxAmplitude}.
*
* <p>Devices with no frequency control will collapse all frequencies to zero and leave
* amplitudes unchanged.
*
* <p>The frequency value returned in segments will be absolute, converted with
* {@link VibratorInfo#getAbsoluteFrequency(float)}.
*/
final class ClippingAmplitudeAndFrequencyAdapter
implements VibrationEffectAdapters.SegmentsAdapter<VibratorInfo> {
@Override
public int apply(List<VibrationEffectSegment> segments, int repeatIndex, VibratorInfo info) {
int segmentCount = segments.size();
for (int i = 0; i < segmentCount; i++) {
VibrationEffectSegment segment = segments.get(i);
if (segment instanceof StepSegment) {
segments.set(i, apply((StepSegment) segment, info));
} else if (segment instanceof RampSegment) {
segments.set(i, apply((RampSegment) segment, info));
}
}
return repeatIndex;
}
private StepSegment apply(StepSegment segment, VibratorInfo info) {
float clampedFrequency = clampFrequency(info, segment.getFrequency());
return new StepSegment(
clampAmplitude(info, clampedFrequency, segment.getAmplitude()),
info.getAbsoluteFrequency(clampedFrequency),
(int) segment.getDuration());
}
private RampSegment apply(RampSegment segment, VibratorInfo info) {
float clampedStartFrequency = clampFrequency(info, segment.getStartFrequency());
float clampedEndFrequency = clampFrequency(info, segment.getEndFrequency());
return new RampSegment(
clampAmplitude(info, clampedStartFrequency, segment.getStartAmplitude()),
clampAmplitude(info, clampedEndFrequency, segment.getEndAmplitude()),
info.getAbsoluteFrequency(clampedStartFrequency),
info.getAbsoluteFrequency(clampedEndFrequency),
(int) segment.getDuration());
}
private float clampFrequency(VibratorInfo info, float frequency) {
return info.getFrequencyRange().clamp(frequency);
}
private float clampAmplitude(VibratorInfo info, float frequency, float amplitude) {
return MathUtils.min(amplitude, info.getMaxAmplitude(frequency));
}
}

View File

@@ -16,231 +16,33 @@
package com.android.server.vibrator;
import android.hardware.vibrator.IVibrator;
import android.os.VibrationEffect;
import android.os.VibratorInfo;
import android.os.vibrator.RampSegment;
import android.os.vibrator.StepSegment;
import android.os.vibrator.VibrationEffectSegment;
import android.util.MathUtils;
import android.util.Range;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/** Adapts a {@link VibrationEffect} to a specific device, taking into account its capabilities. */
final class DeviceVibrationEffectAdapter implements VibrationEffectModifier<VibratorInfo> {
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;
/** Adapts a sequence of {@link VibrationEffectSegment} to device's capabilities. */
interface SegmentsAdapter {
/**
* Modifies the given segments list by adding/removing segments to it based on the
* device capabilities specified by given {@link VibratorInfo}.
*
* @param segments List of {@link VibrationEffectSegment} to be modified.
* @param repeatIndex Repeat index of the vibration with given segment list.
* @param info The device vibrator info that the segments must be adapted to.
* @return The new repeat index to be used for the modified list.
*/
int apply(List<VibrationEffectSegment> segments, int repeatIndex, VibratorInfo info);
}
private final SegmentsAdapter mAmplitudeFrequencyAdapter;
private final SegmentsAdapter mStepToRampAdapter;
private final SegmentsAdapter mRampToStepsAdapter;
private final List<VibrationEffectAdapters.SegmentsAdapter<VibratorInfo>> mSegmentAdapters;
DeviceVibrationEffectAdapter() {
this(new ClippingAmplitudeFrequencyAdapter());
}
DeviceVibrationEffectAdapter(SegmentsAdapter amplitudeFrequencyAdapter) {
mAmplitudeFrequencyAdapter = amplitudeFrequencyAdapter;
mStepToRampAdapter = new StepToRampAdapter();
mRampToStepsAdapter = new RampToStepsAdapter(RAMP_STEP_DURATION_MILLIS);
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 ClippingAmplitudeAndFrequencyAdapter()
);
}
@Override
public VibrationEffect apply(VibrationEffect effect, VibratorInfo info) {
if (!(effect instanceof VibrationEffect.Composed)) {
return effect;
}
VibrationEffect.Composed composed = (VibrationEffect.Composed) effect;
List<VibrationEffectSegment> newSegments = new ArrayList<>(composed.getSegments());
int newRepeatIndex = composed.getRepeatIndex();
// Replace ramps with a sequence of fixed steps, or no-op if PWLE capability present.
newRepeatIndex = mRampToStepsAdapter.apply(newSegments, newRepeatIndex, info);
// Replace steps that should be handled by PWLE to ramps, or no-op if capability missing.
// This should be done before frequency is converted from relative to absolute values.
newRepeatIndex = mStepToRampAdapter.apply(newSegments, newRepeatIndex, info);
// Adapt amplitude and frequency values to device supported ones, converting frequency
// to absolute values in Hertz.
newRepeatIndex = mAmplitudeFrequencyAdapter.apply(newSegments, newRepeatIndex, info);
// TODO(b/167947076): add filter that removes unsupported primitives
// TODO(b/167947076): add filter that replaces unsupported prebaked with fallback
return new VibrationEffect.Composed(newSegments, newRepeatIndex);
}
/**
* 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.
*/
private static final class StepToRampAdapter implements SegmentsAdapter {
@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.
return repeatIndex;
}
int segmentCount = segments.size();
// 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) {
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++) {
if (segments.get(i) instanceof RampSegment) {
for (int j = i - 1; j >= 0 && (segments.get(j) instanceof StepSegment); j--) {
segments.set(j, apply((StepSegment) segments.get(j)));
}
}
}
return repeatIndex;
}
private RampSegment apply(StepSegment segment) {
return new RampSegment(segment.getAmplitude(), segment.getAmplitude(),
segment.getFrequency(), segment.getFrequency(), (int) segment.getDuration());
}
}
/**
* 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.
*/
private static final class RampToStepsAdapter implements SegmentsAdapter {
private final int mStepDuration;
RampToStepsAdapter(int stepDuration) {
mStepDuration = stepDuration;
}
@Override
public int apply(List<VibrationEffectSegment> segments, int repeatIndex,
VibratorInfo info) {
if (info.hasCapability(IVibrator.CAP_COMPOSE_PWLE_EFFECTS)) {
// The vibrator have PWLE capability, so keep the segments unchanged.
return repeatIndex;
}
int segmentCount = segments.size();
for (int i = 0; i < segmentCount; i++) {
VibrationEffectSegment segment = segments.get(i);
if (!(segment instanceof RampSegment)) {
continue;
}
List<StepSegment> steps = apply((RampSegment) segment);
segments.remove(i);
segments.addAll(i, steps);
int addedSegments = steps.size() - 1;
if (repeatIndex > i) {
repeatIndex += addedSegments;
}
i += addedSegments;
segmentCount += addedSegments;
}
return repeatIndex;
}
private List<StepSegment> apply(RampSegment ramp) {
if (Float.compare(ramp.getStartAmplitude(), ramp.getEndAmplitude()) == 0) {
// Amplitude is the same, so return a single step to simulate this ramp.
return Arrays.asList(
new StepSegment(ramp.getStartAmplitude(), ramp.getStartFrequency(),
(int) ramp.getDuration()));
}
List<StepSegment> steps = new ArrayList<>();
int stepCount = (int) (ramp.getDuration() + mStepDuration - 1) / mStepDuration;
for (int i = 0; i < stepCount - 1; i++) {
float pos = (float) i / stepCount;
steps.add(new StepSegment(
interpolate(ramp.getStartAmplitude(), ramp.getEndAmplitude(), pos),
interpolate(ramp.getStartFrequency(), ramp.getEndFrequency(), pos),
mStepDuration));
}
int duration = (int) ramp.getDuration() - mStepDuration * (stepCount - 1);
steps.add(new StepSegment(ramp.getEndAmplitude(), ramp.getEndFrequency(), duration));
return steps;
}
private static float interpolate(float start, float end, float position) {
return start + position * (end - start);
}
}
/**
* Adapter that clips frequency values to {@link VibratorInfo#getFrequencyRange()} and
* amplitude values to respective {@link VibratorInfo#getMaxAmplitude}.
*
* <p>Devices with no frequency control will collapse all frequencies to zero and leave
* amplitudes unchanged.
*
* <p>The frequency value returned in segments will be absolute, conveted with
* {@link VibratorInfo#getAbsoluteFrequency(float)}.
*/
private static final class ClippingAmplitudeFrequencyAdapter implements SegmentsAdapter {
@Override
public int apply(List<VibrationEffectSegment> segments, int repeatIndex,
VibratorInfo info) {
int segmentCount = segments.size();
for (int i = 0; i < segmentCount; i++) {
VibrationEffectSegment segment = segments.get(i);
if (segment instanceof StepSegment) {
segments.set(i, apply((StepSegment) segment, info));
} else if (segment instanceof RampSegment) {
segments.set(i, apply((RampSegment) segment, info));
}
}
return repeatIndex;
}
private StepSegment apply(StepSegment segment, VibratorInfo info) {
float clampedFrequency = info.getFrequencyRange().clamp(segment.getFrequency());
return new StepSegment(
MathUtils.min(segment.getAmplitude(), info.getMaxAmplitude(clampedFrequency)),
info.getAbsoluteFrequency(clampedFrequency),
(int) segment.getDuration());
}
private RampSegment apply(RampSegment segment, VibratorInfo info) {
Range<Float> frequencyRange = info.getFrequencyRange();
float clampedStartFrequency = frequencyRange.clamp(segment.getStartFrequency());
float clampedEndFrequency = frequencyRange.clamp(segment.getEndFrequency());
return new RampSegment(
MathUtils.min(segment.getStartAmplitude(),
info.getMaxAmplitude(clampedStartFrequency)),
MathUtils.min(segment.getEndAmplitude(),
info.getMaxAmplitude(clampedEndFrequency)),
info.getAbsoluteFrequency(clampedStartFrequency),
info.getAbsoluteFrequency(clampedEndFrequency),
(int) segment.getDuration());
}
return VibrationEffectAdapters.apply(effect, mSegmentAdapters, info);
}
}

View File

@@ -0,0 +1,93 @@
/*
* 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 android.hardware.vibrator.IVibrator;
import android.os.VibratorInfo;
import android.os.vibrator.RampSegment;
import android.os.vibrator.StepSegment;
import android.os.vibrator.VibrationEffectSegment;
import java.util.ArrayList;
import java.util.Arrays;
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.
*/
final class RampToStepAdapter implements VibrationEffectAdapters.SegmentsAdapter<VibratorInfo> {
private final int mStepDuration;
RampToStepAdapter(int stepDuration) {
mStepDuration = stepDuration;
}
@Override
public int apply(List<VibrationEffectSegment> segments, int repeatIndex,
VibratorInfo info) {
if (info.hasCapability(IVibrator.CAP_COMPOSE_PWLE_EFFECTS)) {
// The vibrator have PWLE capability, so keep the segments unchanged.
return repeatIndex;
}
int segmentCount = segments.size();
for (int i = 0; i < segmentCount; i++) {
VibrationEffectSegment segment = segments.get(i);
if (!(segment instanceof RampSegment)) {
continue;
}
List<StepSegment> steps = apply((RampSegment) segment);
segments.remove(i);
segments.addAll(i, steps);
int addedSegments = steps.size() - 1;
if (repeatIndex > i) {
repeatIndex += addedSegments;
}
i += addedSegments;
segmentCount += addedSegments;
}
return repeatIndex;
}
private List<StepSegment> apply(RampSegment ramp) {
if (Float.compare(ramp.getStartAmplitude(), ramp.getEndAmplitude()) == 0) {
// Amplitude is the same, so return a single step to simulate this ramp.
return Arrays.asList(
new StepSegment(ramp.getStartAmplitude(), ramp.getStartFrequency(),
(int) ramp.getDuration()));
}
List<StepSegment> steps = new ArrayList<>();
int stepCount = (int) (ramp.getDuration() + mStepDuration - 1) / mStepDuration;
for (int i = 0; i < stepCount - 1; i++) {
float pos = (float) i / stepCount;
steps.add(new StepSegment(
interpolate(ramp.getStartAmplitude(), ramp.getEndAmplitude(), pos),
interpolate(ramp.getStartFrequency(), ramp.getEndFrequency(), pos),
mStepDuration));
}
int duration = (int) ramp.getDuration() - mStepDuration * (stepCount - 1);
steps.add(new StepSegment(ramp.getEndAmplitude(), ramp.getEndFrequency(), duration));
return steps;
}
private static float interpolate(float start, float end, float position) {
return start + position * (end - start);
}
}

View File

@@ -0,0 +1,65 @@
/*
* 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 android.hardware.vibrator.IVibrator;
import android.os.VibratorInfo;
import android.os.vibrator.RampSegment;
import android.os.vibrator.StepSegment;
import android.os.vibrator.VibrationEffectSegment;
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.
*/
final class StepToRampAdapter implements VibrationEffectAdapters.SegmentsAdapter<VibratorInfo> {
@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.
return repeatIndex;
}
int segmentCount = segments.size();
// 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) {
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++) {
if (segments.get(i) instanceof RampSegment) {
for (int j = i - 1; j >= 0 && (segments.get(j) instanceof StepSegment); j--) {
segments.set(j, apply((StepSegment) segments.get(j)));
}
}
}
return repeatIndex;
}
private RampSegment apply(StepSegment segment) {
return new RampSegment(segment.getAmplitude(), segment.getAmplitude(),
segment.getFrequency(), segment.getFrequency(), (int) segment.getDuration());
}
}

View File

@@ -0,0 +1,92 @@
/*
* 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 android.os.VibrationEffect;
import android.os.vibrator.VibrationEffectSegment;
import java.util.ArrayList;
import java.util.List;
/**
* Helpers to adapt a {@link VibrationEffect} to generic modifiers (e.g. device capabilities,
* user settings, etc).
*/
public final class VibrationEffectAdapters {
/**
* Function that applies a generic modifier to a sequence of {@link VibrationEffectSegment}.
*
* @param <T> The type of modifiers this adapter accepts.
*/
public interface SegmentsAdapter<T> {
/**
* Add and/or remove segments to the given {@link VibrationEffectSegment} list based on the
* given modifier.
*
* <p>This returns the new {@code repeatIndex} to be used together with the updated list to
* specify an equivalent {@link VibrationEffect}.
*
* @param segments List of {@link VibrationEffectSegment} to be modified.
* @param repeatIndex Repeat index on the current segment list.
* @param modifier The modifier to be applied to the sequence of segments.
* @return The new repeat index on the modifies list.
*/
int apply(List<VibrationEffectSegment> segments, int repeatIndex, T modifier);
}
/**
* Function that applies a generic modifier to a {@link VibrationEffect}.
*
* @param <T> The type of modifiers this adapter accepts.
*/
public interface EffectAdapter<T> {
/** Applies the modifier to given {@link VibrationEffect}, returning the new effect. */
VibrationEffect apply(VibrationEffect effect, T modifier);
}
/**
* Applies a sequence of {@link SegmentsAdapter} to the segments of a given
* {@link VibrationEffect}, in order.
*
* @param effect The effect to be adapted to given modifier.
* @param adapters The sequence of adapters to be applied to given {@link VibrationEffect}.
* @param modifier The modifier to be passed to each adapter that describes the conditions the
* {@link VibrationEffect} needs to be adapted to (e.g. device capabilities,
* user settings, etc).
*/
public static <T> VibrationEffect apply(VibrationEffect effect,
List<SegmentsAdapter<T>> adapters, T modifier) {
if (!(effect instanceof VibrationEffect.Composed)) {
// Segments adapters can only be applied to Composed effects.
return effect;
}
VibrationEffect.Composed composed = (VibrationEffect.Composed) effect;
List<VibrationEffectSegment> newSegments = new ArrayList<>(composed.getSegments());
int newRepeatIndex = composed.getRepeatIndex();
int adapterCount = adapters.size();
for (int i = 0; i < adapterCount; i++) {
newRepeatIndex = adapters.get(i).apply(newSegments, newRepeatIndex, modifier);
}
return new VibrationEffect.Composed(newSegments, newRepeatIndex);
}
}

View File

@@ -1,26 +0,0 @@
/*
* 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 android.os.VibrationEffect;
/** Function that applies a generic modifier to a {@link VibrationEffect}. */
interface VibrationEffectModifier<T> {
/** Applies the modifier to given {@link VibrationEffect}. */
VibrationEffect apply(VibrationEffect effect, T modifier);
}

View File

@@ -95,7 +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 VibrationEffectModifier<VibratorInfo> mDeviceEffectAdapter =
private final DeviceVibrationEffectAdapter mDeviceEffectAdapter =
new DeviceVibrationEffectAdapter();
private final Vibration mVibration;
private final VibrationCallbacks mCallbacks;