• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.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.NAMESPACE;
22 import static com.android.internal.vibrator.persistence.XmlConstants.TAG_REPEATING;
23 import static com.android.internal.vibrator.persistence.XmlConstants.TAG_WAVEFORM_EFFECT;
24 import static com.android.internal.vibrator.persistence.XmlConstants.TAG_WAVEFORM_ENTRY;
25 import static com.android.internal.vibrator.persistence.XmlConstants.VALUE_AMPLITUDE_DEFAULT;
26 
27 import android.annotation.NonNull;
28 import android.os.VibrationEffect;
29 import android.util.IntArray;
30 import android.util.LongArray;
31 
32 import com.android.internal.vibrator.persistence.SerializedVibrationEffect.SerializedSegment;
33 import com.android.modules.utils.TypedXmlPullParser;
34 import com.android.modules.utils.TypedXmlSerializer;
35 
36 import java.io.IOException;
37 import java.util.Arrays;
38 
39 /**
40  * Serialized representation of a waveform effect created via
41  * {@link VibrationEffect#createWaveform(long[], int[], int)}.
42  *
43  * @hide
44  */
45 final class SerializedAmplitudeStepWaveform implements SerializedSegment {
46 
47     @NonNull private final long[] mTimings;
48     @NonNull private final int[] mAmplitudes;
49     private final int mRepeatIndex;
50 
SerializedAmplitudeStepWaveform(long[] timings, int[] amplitudes, int repeatIndex)51     private SerializedAmplitudeStepWaveform(long[] timings, int[] amplitudes, int repeatIndex) {
52         mTimings = timings;
53         mAmplitudes = amplitudes;
54         mRepeatIndex = repeatIndex;
55     }
56 
57     @Override
deserializeIntoComposition(@onNull VibrationEffect.Composition composition)58     public void deserializeIntoComposition(@NonNull VibrationEffect.Composition composition) {
59         composition.addEffect(VibrationEffect.createWaveform(mTimings, mAmplitudes, mRepeatIndex));
60     }
61 
62     @Override
write(@onNull TypedXmlSerializer serializer)63     public void write(@NonNull TypedXmlSerializer serializer) throws IOException {
64         serializer.startTag(NAMESPACE, TAG_WAVEFORM_EFFECT);
65 
66         for (int i = 0; i < mTimings.length; i++) {
67             if (i == mRepeatIndex) {
68                 serializer.startTag(NAMESPACE, TAG_REPEATING);
69             }
70             writeWaveformEntry(serializer, i);
71         }
72 
73         if (mRepeatIndex >= 0) {
74             serializer.endTag(NAMESPACE, TAG_REPEATING);
75         }
76         serializer.endTag(NAMESPACE, TAG_WAVEFORM_EFFECT);
77     }
78 
writeWaveformEntry(@onNull TypedXmlSerializer serializer, int index)79     private void writeWaveformEntry(@NonNull TypedXmlSerializer serializer, int index)
80             throws IOException {
81         serializer.startTag(NAMESPACE, TAG_WAVEFORM_ENTRY);
82 
83         if (mAmplitudes[index] == VibrationEffect.DEFAULT_AMPLITUDE) {
84             serializer.attribute(NAMESPACE, ATTRIBUTE_AMPLITUDE, VALUE_AMPLITUDE_DEFAULT);
85         } else {
86             serializer.attributeInt(NAMESPACE, ATTRIBUTE_AMPLITUDE, mAmplitudes[index]);
87         }
88 
89         serializer.attributeLong(NAMESPACE, ATTRIBUTE_DURATION_MS, mTimings[index]);
90         serializer.endTag(NAMESPACE, TAG_WAVEFORM_ENTRY);
91     }
92 
93     @Override
toString()94     public String toString() {
95         return "SerializedAmplitudeStepWaveform{"
96                 + "timings=" + Arrays.toString(mTimings)
97                 + ", amplitudes=" + Arrays.toString(mAmplitudes)
98                 + ", repeatIndex=" + mRepeatIndex
99                 + '}';
100     }
101 
102     /** Builder for {@link SerializedAmplitudeStepWaveform}. */
103     static final class Builder {
104         private final LongArray mTimings = new LongArray();
105         private final IntArray mAmplitudes = new IntArray();
106         private int mRepeatIndex = -1;
107 
addDurationAndAmplitude(long durationMs, int amplitude)108         void addDurationAndAmplitude(long durationMs, int amplitude) {
109             mTimings.add(durationMs);
110             mAmplitudes.add(amplitude);
111         }
112 
setRepeatIndexToCurrentEntry()113         void setRepeatIndexToCurrentEntry() {
114             mRepeatIndex = mTimings.size();
115         }
116 
hasNonZeroDuration()117         boolean hasNonZeroDuration() {
118             for (int i = 0; i < mTimings.size(); i++) {
119                 if (mTimings.get(i) > 0) {
120                     return true;
121                 }
122             }
123             return false;
124         }
125 
build()126         SerializedAmplitudeStepWaveform build() {
127             return new SerializedAmplitudeStepWaveform(
128                     mTimings.toArray(), mAmplitudes.toArray(), mRepeatIndex);
129         }
130     }
131 
132     /** Parser implementation for the {@link XmlConstants#TAG_WAVEFORM_EFFECT}. */
133     static final class Parser {
134 
135         @NonNull
parseNext(@onNull TypedXmlPullParser parser)136         static SerializedAmplitudeStepWaveform parseNext(@NonNull TypedXmlPullParser parser)
137                 throws XmlParserException, IOException {
138             XmlValidator.checkStartTag(parser, TAG_WAVEFORM_EFFECT);
139             XmlValidator.checkTagHasNoUnexpectedAttributes(parser);
140 
141             Builder waveformBuilder = new Builder();
142             int outerDepth = parser.getDepth();
143 
144             // Read all nested tag that is not a repeating tag as a waveform entry.
145             while (XmlReader.readNextTagWithin(parser, outerDepth)
146                     && !TAG_REPEATING.equals(parser.getName())) {
147                 parseWaveformEntry(parser, waveformBuilder);
148             }
149 
150             // If found a repeating tag, read its content.
151             if (TAG_REPEATING.equals(parser.getName())) {
152                 parseRepeating(parser, waveformBuilder);
153             }
154 
155             // Check schema assertions about <waveform-effect>
156             XmlValidator.checkParserCondition(waveformBuilder.hasNonZeroDuration(),
157                     "Unexpected %s tag with total duration zero", TAG_WAVEFORM_EFFECT);
158 
159             // Consume tag
160             XmlReader.readEndTag(parser, TAG_WAVEFORM_EFFECT, outerDepth);
161 
162             return waveformBuilder.build();
163         }
164 
parseRepeating(TypedXmlPullParser parser, Builder waveformBuilder)165         private static void parseRepeating(TypedXmlPullParser parser, Builder waveformBuilder)
166                 throws XmlParserException, IOException {
167             XmlValidator.checkStartTag(parser, TAG_REPEATING);
168             XmlValidator.checkTagHasNoUnexpectedAttributes(parser);
169 
170             waveformBuilder.setRepeatIndexToCurrentEntry();
171 
172             boolean hasEntry = false;
173             int outerDepth = parser.getDepth();
174             while (XmlReader.readNextTagWithin(parser, outerDepth)) {
175                 parseWaveformEntry(parser, waveformBuilder);
176                 hasEntry = true;
177             }
178 
179             // Check schema assertions about <repeating>
180             XmlValidator.checkParserCondition(hasEntry, "Unexpected empty %s tag", TAG_REPEATING);
181 
182             // Consume tag
183             XmlReader.readEndTag(parser, TAG_REPEATING, outerDepth);
184         }
185 
parseWaveformEntry(TypedXmlPullParser parser, Builder waveformBuilder)186         private static void parseWaveformEntry(TypedXmlPullParser parser, Builder waveformBuilder)
187                 throws XmlParserException, IOException {
188             XmlValidator.checkStartTag(parser, TAG_WAVEFORM_ENTRY);
189             XmlValidator.checkTagHasNoUnexpectedAttributes(
190                     parser, ATTRIBUTE_DURATION_MS, ATTRIBUTE_AMPLITUDE);
191 
192             String rawAmplitude = parser.getAttributeValue(NAMESPACE, ATTRIBUTE_AMPLITUDE);
193             int amplitude = VALUE_AMPLITUDE_DEFAULT.equals(rawAmplitude)
194                     ? VibrationEffect.DEFAULT_AMPLITUDE
195                     : XmlReader.readAttributeIntInRange(
196                             parser, ATTRIBUTE_AMPLITUDE, 0, VibrationEffect.MAX_AMPLITUDE);
197             int durationMs = XmlReader.readAttributeIntNonNegative(parser, ATTRIBUTE_DURATION_MS);
198 
199             waveformBuilder.addDurationAndAmplitude(durationMs, amplitude);
200 
201             // Consume tag
202             XmlReader.readEndTag(parser);
203         }
204     }
205 }
206