1 /*
2  * Copyright (C) 2021 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.server.vibrator;
18 
19 import android.hardware.vibrator.IVibrator;
20 import android.os.VibratorInfo;
21 import android.os.vibrator.RampSegment;
22 import android.os.vibrator.StepSegment;
23 import android.os.vibrator.VibrationEffectSegment;
24 
25 import java.util.ArrayList;
26 import java.util.List;
27 
28 /**
29  * Adapter that converts step segments that should be handled as PWLEs to ramp segments.
30  *
31  * <p>Each replaced {@link StepSegment} will be represented by a {@link RampSegment} with same
32  * start and end amplitudes/frequencies, which can then be converted to PWLE compositions. This
33  * adapter leaves the segments unchanged if the device doesn't have the PWLE composition capability.
34  */
35 final class StepToRampAdapter implements VibrationEffectAdapters.SegmentsAdapter<VibratorInfo> {
36 
37     @Override
apply(List<VibrationEffectSegment> segments, int repeatIndex, VibratorInfo info)38     public int apply(List<VibrationEffectSegment> segments, int repeatIndex,
39             VibratorInfo info) {
40         if (!info.hasCapability(IVibrator.CAP_COMPOSE_PWLE_EFFECTS)) {
41             // The vibrator does not have PWLE capability, so keep the segments unchanged.
42             return repeatIndex;
43         }
44         convertStepsToRamps(segments);
45         repeatIndex = splitLongRampSegments(info, segments, repeatIndex);
46         return repeatIndex;
47     }
48 
convertStepsToRamps(List<VibrationEffectSegment> segments)49     private void convertStepsToRamps(List<VibrationEffectSegment> segments) {
50         int segmentCount = segments.size();
51         // Convert steps that require frequency control to ramps.
52         for (int i = 0; i < segmentCount; i++) {
53             VibrationEffectSegment segment = segments.get(i);
54             if (isStep(segment) && ((StepSegment) segment).getFrequency() != 0) {
55                 segments.set(i, convertStepToRamp((StepSegment) segment));
56             }
57         }
58         // Convert steps that are next to ramps to also become ramps, so they can be composed
59         // together in the same PWLE waveform.
60         for (int i = 0; i < segmentCount; i++) {
61             if (segments.get(i) instanceof RampSegment) {
62                 for (int j = i - 1; j >= 0 && isStep(segments.get(j)); j--) {
63                     segments.set(j, convertStepToRamp((StepSegment) segments.get(j)));
64                 }
65                 for (int j = i + 1; j < segmentCount && isStep(segments.get(j)); j++) {
66                     segments.set(j, convertStepToRamp((StepSegment) segments.get(j)));
67                 }
68             }
69         }
70     }
71 
72     /**
73      * Split {@link RampSegment} entries that have duration longer than {@link
74      * VibratorInfo#getPwlePrimitiveDurationMax()}.
75      */
splitLongRampSegments(VibratorInfo info, List<VibrationEffectSegment> segments, int repeatIndex)76     private int splitLongRampSegments(VibratorInfo info, List<VibrationEffectSegment> segments,
77             int repeatIndex) {
78         int maxDuration = info.getPwlePrimitiveDurationMax();
79         if (maxDuration <= 0) {
80             // No limit set to PWLE primitive duration.
81             return repeatIndex;
82         }
83 
84         int segmentCount = segments.size();
85         for (int i = 0; i < segmentCount; i++) {
86             if (!(segments.get(i) instanceof RampSegment)) {
87                 continue;
88             }
89             RampSegment ramp = (RampSegment) segments.get(i);
90             int splits = ((int) ramp.getDuration() + maxDuration - 1) / maxDuration;
91             if (splits <= 1) {
92                 continue;
93             }
94             segments.remove(i);
95             segments.addAll(i, splitRampSegment(ramp, splits));
96             int addedSegments = splits - 1;
97             if (repeatIndex > i) {
98                 repeatIndex += addedSegments;
99             }
100             i += addedSegments;
101             segmentCount += addedSegments;
102         }
103 
104         return repeatIndex;
105     }
106 
convertStepToRamp(StepSegment segment)107     private static RampSegment convertStepToRamp(StepSegment segment) {
108         return new RampSegment(segment.getAmplitude(), segment.getAmplitude(),
109                 segment.getFrequency(), segment.getFrequency(), (int) segment.getDuration());
110     }
111 
splitRampSegment(RampSegment ramp, int splits)112     private static List<RampSegment> splitRampSegment(RampSegment ramp, int splits) {
113         List<RampSegment> ramps = new ArrayList<>(splits);
114         long splitDuration = ramp.getDuration() / splits;
115         float previousAmplitude = ramp.getStartAmplitude();
116         float previousFrequency = ramp.getStartFrequency();
117         long accumulatedDuration = 0;
118 
119         for (int i = 1; i < splits; i++) {
120             accumulatedDuration += splitDuration;
121             RampSegment rampSplit = new RampSegment(
122                     previousAmplitude, interpolateAmplitude(ramp, accumulatedDuration),
123                     previousFrequency, interpolateFrequency(ramp, accumulatedDuration),
124                     (int) splitDuration);
125             ramps.add(rampSplit);
126             previousAmplitude = rampSplit.getEndAmplitude();
127             previousFrequency = rampSplit.getEndFrequency();
128         }
129 
130         ramps.add(new RampSegment(previousAmplitude, ramp.getEndAmplitude(), previousFrequency,
131                 ramp.getEndFrequency(), (int) (ramp.getDuration() - accumulatedDuration)));
132 
133         return ramps;
134     }
135 
isStep(VibrationEffectSegment segment)136     private static boolean isStep(VibrationEffectSegment segment) {
137         return segment instanceof StepSegment;
138     }
139 
interpolateAmplitude(RampSegment ramp, long duration)140     private static float interpolateAmplitude(RampSegment ramp, long duration) {
141         return interpolate(ramp.getStartAmplitude(), ramp.getEndAmplitude(), duration,
142                 ramp.getDuration());
143     }
144 
interpolateFrequency(RampSegment ramp, long duration)145     private static float interpolateFrequency(RampSegment ramp, long duration) {
146         return interpolate(ramp.getStartFrequency(), ramp.getEndFrequency(), duration,
147                 ramp.getDuration());
148     }
149 
interpolate(float start, float end, long duration, long totalDuration)150     private static float interpolate(float start, float end, long duration, long totalDuration) {
151         float position = (float) duration / totalDuration;
152         return start + position * (end - start);
153     }
154 }
155