1 /*
2  * Copyright (C) 2023 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.VibrationEffectSegment;
23 import android.util.MathUtils;
24 
25 import java.util.ArrayList;
26 import java.util.List;
27 
28 /**
29  * Adapter that splits segments with longer duration than the device capabilities.
30  *
31  * <p>This transformation replaces large {@link RampSegment} entries by a sequence of smaller
32  * ramp segments that starts and ends at the same amplitudes/frequencies, interpolating the
33  * intermediate values.
34  *
35  * <p>The segments will not be changed if the device doesn't have
36  * {@link IVibrator#CAP_COMPOSE_PWLE_EFFECTS}.
37  */
38 final class SplitSegmentsAdapter implements VibrationSegmentsAdapter {
39 
40     @Override
adaptToVibrator(VibratorInfo info, List<VibrationEffectSegment> segments, int repeatIndex)41     public int adaptToVibrator(VibratorInfo info, List<VibrationEffectSegment> segments,
42             int repeatIndex) {
43         if (!info.hasCapability(IVibrator.CAP_COMPOSE_PWLE_EFFECTS)) {
44             // The vibrator does not have PWLE capability, so keep the segments unchanged.
45             return repeatIndex;
46         }
47         int maxRampDuration = info.getPwlePrimitiveDurationMax();
48         if (maxRampDuration <= 0) {
49             // No limit set to PWLE primitive duration.
50             return repeatIndex;
51         }
52 
53         int segmentCount = segments.size();
54         for (int i = 0; i < segmentCount; i++) {
55             if (!(segments.get(i) instanceof RampSegment)) {
56                 continue;
57             }
58             RampSegment ramp = (RampSegment) segments.get(i);
59             int splits = ((int) ramp.getDuration() + maxRampDuration - 1) / maxRampDuration;
60             if (splits <= 1) {
61                 continue;
62             }
63             segments.remove(i);
64             segments.addAll(i, splitRampSegment(info, ramp, splits));
65             int addedSegments = splits - 1;
66             if (repeatIndex > i) {
67                 repeatIndex += addedSegments;
68             }
69             i += addedSegments;
70             segmentCount += addedSegments;
71         }
72 
73         return repeatIndex;
74     }
75 
splitRampSegment(VibratorInfo info, RampSegment ramp, int splits)76     private static List<RampSegment> splitRampSegment(VibratorInfo info, RampSegment ramp,
77             int splits) {
78         List<RampSegment> ramps = new ArrayList<>(splits);
79         // Fill zero frequency values with the device resonant frequency before interpolating.
80         float startFrequencyHz = fillEmptyFrequency(info, ramp.getStartFrequencyHz());
81         float endFrequencyHz = fillEmptyFrequency(info, ramp.getEndFrequencyHz());
82         long splitDuration = ramp.getDuration() / splits;
83         float previousAmplitude = ramp.getStartAmplitude();
84         float previousFrequencyHz = startFrequencyHz;
85         long accumulatedDuration = 0;
86 
87         for (int i = 1; i < splits; i++) {
88             accumulatedDuration += splitDuration;
89             float durationRatio = (float) accumulatedDuration / ramp.getDuration();
90             float interpolatedFrequency =
91                     MathUtils.lerp(startFrequencyHz, endFrequencyHz, durationRatio);
92             float interpolatedAmplitude =
93                     MathUtils.lerp(ramp.getStartAmplitude(), ramp.getEndAmplitude(), durationRatio);
94             RampSegment rampSplit = new RampSegment(
95                     previousAmplitude, interpolatedAmplitude,
96                     previousFrequencyHz, interpolatedFrequency,
97                     (int) splitDuration);
98             ramps.add(rampSplit);
99             previousAmplitude = rampSplit.getEndAmplitude();
100             previousFrequencyHz = rampSplit.getEndFrequencyHz();
101         }
102 
103         ramps.add(new RampSegment(previousAmplitude, ramp.getEndAmplitude(), previousFrequencyHz,
104                 endFrequencyHz, (int) (ramp.getDuration() - accumulatedDuration)));
105 
106         return ramps;
107     }
108 
fillEmptyFrequency(VibratorInfo info, float frequencyHz)109     private static float fillEmptyFrequency(VibratorInfo info, float frequencyHz) {
110         if (Float.isNaN(info.getResonantFrequencyHz())) {
111             return frequencyHz;
112         }
113         return frequencyHz == 0 ? info.getResonantFrequencyHz() : frequencyHz;
114     }
115 }
116