Loading services/core/java/com/android/server/vibrator/ClippingAmplitudeAndFrequencyAdapter.java 0 → 100644 +80 −0 Original line number Diff line number Diff line /* * 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)); } } services/core/java/com/android/server/vibrator/DeviceVibrationEffectAdapter.java +12 −210 Original line number Diff line number Diff line Loading @@ -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); } @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); mSegmentAdapters = Arrays.asList( // 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); new RampToStepAdapter(RAMP_STEP_DURATION_MILLIS), new StepToRampAdapter(), new ClippingAmplitudeAndFrequencyAdapter() ); } /** * 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()); } public VibrationEffect apply(VibrationEffect effect, VibratorInfo info) { return VibrationEffectAdapters.apply(effect, mSegmentAdapters, info); } } services/core/java/com/android/server/vibrator/RampToStepAdapter.java 0 → 100644 +93 −0 Original line number Diff line number Diff line /* * 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); } } services/core/java/com/android/server/vibrator/StepToRampAdapter.java 0 → 100644 +65 −0 Original line number Diff line number Diff line /* * 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()); } } services/core/java/com/android/server/vibrator/VibrationEffectAdapters.java 0 → 100644 +92 −0 Original line number Diff line number Diff line /* * 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); } } Loading
services/core/java/com/android/server/vibrator/ClippingAmplitudeAndFrequencyAdapter.java 0 → 100644 +80 −0 Original line number Diff line number Diff line /* * 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)); } }
services/core/java/com/android/server/vibrator/DeviceVibrationEffectAdapter.java +12 −210 Original line number Diff line number Diff line Loading @@ -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); } @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); mSegmentAdapters = Arrays.asList( // 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); new RampToStepAdapter(RAMP_STEP_DURATION_MILLIS), new StepToRampAdapter(), new ClippingAmplitudeAndFrequencyAdapter() ); } /** * 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()); } public VibrationEffect apply(VibrationEffect effect, VibratorInfo info) { return VibrationEffectAdapters.apply(effect, mSegmentAdapters, info); } }
services/core/java/com/android/server/vibrator/RampToStepAdapter.java 0 → 100644 +93 −0 Original line number Diff line number Diff line /* * 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); } }
services/core/java/com/android/server/vibrator/StepToRampAdapter.java 0 → 100644 +65 −0 Original line number Diff line number Diff line /* * 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()); } }
services/core/java/com/android/server/vibrator/VibrationEffectAdapters.java 0 → 100644 +92 −0 Original line number Diff line number Diff line /* * 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); } }