• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2024 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.internal.vibrator.persistence;
18 
19 import static com.android.internal.vibrator.persistence.XmlConstants.ATTRIBUTE_AMPLITUDE;
20 import static com.android.internal.vibrator.persistence.XmlConstants.ATTRIBUTE_DURATION_MS;
21 import static com.android.internal.vibrator.persistence.XmlConstants.ATTRIBUTE_FREQUENCY_HZ;
22 import static com.android.internal.vibrator.persistence.XmlConstants.ATTRIBUTE_INITIAL_FREQUENCY_HZ;
23 import static com.android.internal.vibrator.persistence.XmlConstants.NAMESPACE;
24 import static com.android.internal.vibrator.persistence.XmlConstants.TAG_CONTROL_POINT;
25 import static com.android.internal.vibrator.persistence.XmlConstants.TAG_WAVEFORM_ENVELOPE_EFFECT;
26 
27 import android.annotation.NonNull;
28 import android.os.VibrationEffect;
29 
30 import com.android.modules.utils.TypedXmlPullParser;
31 import com.android.modules.utils.TypedXmlSerializer;
32 
33 import java.io.IOException;
34 import java.util.ArrayList;
35 import java.util.Arrays;
36 import java.util.List;
37 import java.util.Locale;
38 
39 /**
40  * Serialized representation of a waveform envelope effect created via
41  * {@link VibrationEffect.WaveformEnvelopeBuilder}.
42  *
43  * @hide
44  */
45 final class SerializedWaveformEnvelopeEffect implements SerializedComposedEffect.SerializedSegment {
46 
47     private final WaveformControlPoint[] mControlPoints;
48     private final float mInitialFrequency;
49 
SerializedWaveformEnvelopeEffect(WaveformControlPoint[] controlPoints, float initialFrequency)50     SerializedWaveformEnvelopeEffect(WaveformControlPoint[] controlPoints, float initialFrequency) {
51         mControlPoints = controlPoints;
52         mInitialFrequency = initialFrequency;
53     }
54 
55     @Override
write(@onNull TypedXmlSerializer serializer)56     public void write(@NonNull TypedXmlSerializer serializer) throws IOException {
57         serializer.startTag(NAMESPACE, TAG_WAVEFORM_ENVELOPE_EFFECT);
58 
59         if (!Float.isNaN(mInitialFrequency)) {
60             serializer.attributeFloat(NAMESPACE, ATTRIBUTE_INITIAL_FREQUENCY_HZ, mInitialFrequency);
61         }
62 
63         for (WaveformControlPoint point : mControlPoints) {
64             serializer.startTag(NAMESPACE, TAG_CONTROL_POINT);
65             serializer.attributeFloat(NAMESPACE, ATTRIBUTE_AMPLITUDE, point.mAmplitude);
66             serializer.attributeFloat(NAMESPACE, ATTRIBUTE_FREQUENCY_HZ, point.mFrequency);
67             serializer.attributeLong(NAMESPACE, ATTRIBUTE_DURATION_MS, point.mDurationMs);
68             serializer.endTag(NAMESPACE, TAG_CONTROL_POINT);
69         }
70 
71         serializer.endTag(NAMESPACE, TAG_WAVEFORM_ENVELOPE_EFFECT);
72     }
73 
74     @Override
deserializeIntoComposition(@onNull VibrationEffect.Composition composition)75     public void deserializeIntoComposition(@NonNull VibrationEffect.Composition composition) {
76         VibrationEffect.WaveformEnvelopeBuilder builder =
77                 new VibrationEffect.WaveformEnvelopeBuilder();
78 
79         if (!Float.isNaN(mInitialFrequency)) {
80             builder.setInitialFrequencyHz(mInitialFrequency);
81         }
82 
83         for (WaveformControlPoint point : mControlPoints) {
84             builder.addControlPoint(point.mAmplitude, point.mFrequency, point.mDurationMs);
85         }
86         composition.addEffect(builder.build());
87     }
88 
89     @Override
toString()90     public String toString() {
91         return "SerializedWaveformEnvelopeEffect{"
92                 + "InitialFrequency=" + (Float.isNaN(mInitialFrequency) ? "" : mInitialFrequency)
93                 + ", controlPoints=" + Arrays.toString(mControlPoints)
94                 + '}';
95     }
96 
97     static final class Builder {
98         private final List<WaveformControlPoint> mControlPoints;
99         private float mInitialFrequencyHz = Float.NaN;
100 
Builder()101         Builder() {
102             mControlPoints = new ArrayList<>();
103         }
104 
setInitialFrequencyHz(float frequencyHz)105         void setInitialFrequencyHz(float frequencyHz) {
106             mInitialFrequencyHz = frequencyHz;
107         }
108 
addControlPoint(float amplitude, float frequencyHz, long durationMs)109         void addControlPoint(float amplitude, float frequencyHz, long durationMs) {
110             mControlPoints.add(new WaveformControlPoint(amplitude, frequencyHz, durationMs));
111         }
112 
build()113         SerializedWaveformEnvelopeEffect build() {
114             return new SerializedWaveformEnvelopeEffect(
115                     mControlPoints.toArray(new WaveformControlPoint[0]), mInitialFrequencyHz);
116         }
117     }
118 
119     /** Parser implementation for {@link SerializedWaveformEnvelopeEffect}. */
120     static final class Parser {
121 
122         @NonNull
parseNext(@onNull TypedXmlPullParser parser, @XmlConstants.Flags int flags)123         static SerializedWaveformEnvelopeEffect parseNext(@NonNull TypedXmlPullParser parser,
124                 @XmlConstants.Flags int flags) throws XmlParserException, IOException {
125             XmlValidator.checkStartTag(parser, TAG_WAVEFORM_ENVELOPE_EFFECT);
126             XmlValidator.checkTagHasNoUnexpectedAttributes(parser, ATTRIBUTE_INITIAL_FREQUENCY_HZ);
127 
128             Builder builder = new Builder();
129             builder.setInitialFrequencyHz(
130                     XmlReader.readAttributePositiveFloat(parser, ATTRIBUTE_INITIAL_FREQUENCY_HZ,
131                             Float.NaN));
132 
133             int outerDepth = parser.getDepth();
134 
135             while (XmlReader.readNextTagWithin(parser, outerDepth)) {
136                 parseControlPoint(parser, builder);
137                 // Consume tag
138                 XmlReader.readEndTag(parser);
139             }
140 
141             // Check schema assertions about <waveform-envelope-effect>
142             XmlValidator.checkParserCondition(!builder.mControlPoints.isEmpty(),
143                     "Expected tag %s to have at least one control point",
144                     TAG_WAVEFORM_ENVELOPE_EFFECT);
145 
146             return builder.build();
147         }
148 
parseControlPoint(TypedXmlPullParser parser, Builder builder)149         private static void parseControlPoint(TypedXmlPullParser parser, Builder builder)
150                 throws XmlParserException {
151             XmlValidator.checkStartTag(parser, TAG_CONTROL_POINT);
152             XmlValidator.checkTagHasNoUnexpectedAttributes(
153                     parser, ATTRIBUTE_DURATION_MS, ATTRIBUTE_AMPLITUDE,
154                     ATTRIBUTE_FREQUENCY_HZ);
155             float amplitude = XmlReader.readAttributeFloatInRange(parser, ATTRIBUTE_AMPLITUDE, 0,
156                     1);
157             float frequencyHz = XmlReader.readAttributePositiveFloat(parser,
158                     ATTRIBUTE_FREQUENCY_HZ);
159             long durationMs = XmlReader.readAttributePositiveLong(parser, ATTRIBUTE_DURATION_MS);
160 
161             builder.addControlPoint(amplitude, frequencyHz, durationMs);
162         }
163     }
164 
165     private static final class WaveformControlPoint {
166         private final float mAmplitude;
167         private final float mFrequency;
168         private final long mDurationMs;
169 
WaveformControlPoint(float amplitude, float frequency, long durationMs)170         WaveformControlPoint(float amplitude, float frequency, long durationMs) {
171             mAmplitude = amplitude;
172             mFrequency = frequency;
173             mDurationMs = durationMs;
174         }
175 
176         @Override
toString()177         public String toString() {
178             return String.format(Locale.ROOT, "(%.2f, %.2f, %dms)", mAmplitude, mFrequency,
179                     mDurationMs);
180         }
181     }
182 }
183