• 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 android.os.vibrator.persistence;
18 
19 import static android.os.VibrationEffect.Composition.DELAY_TYPE_PAUSE;
20 import static android.os.VibrationEffect.Composition.DELAY_TYPE_RELATIVE_START_OFFSET;
21 import static android.os.VibrationEffect.Composition.PRIMITIVE_CLICK;
22 import static android.os.VibrationEffect.Composition.PRIMITIVE_LOW_TICK;
23 import static android.os.VibrationEffect.Composition.PRIMITIVE_SPIN;
24 import static android.os.VibrationEffect.Composition.PRIMITIVE_TICK;
25 import static android.os.vibrator.persistence.VibrationXmlParser.isSupportedMimeType;
26 
27 import static com.google.common.truth.Truth.assertThat;
28 
29 import static org.junit.Assert.assertArrayEquals;
30 import static org.junit.Assert.assertThrows;
31 
32 import android.os.PersistableBundle;
33 import android.os.VibrationEffect;
34 import android.os.vibrator.Flags;
35 import android.os.vibrator.PrebakedSegment;
36 import android.os.vibrator.PrimitiveSegment;
37 import android.platform.test.annotations.DisableFlags;
38 import android.platform.test.annotations.EnableFlags;
39 import android.platform.test.flag.junit.SetFlagsRule;
40 import android.util.Xml;
41 
42 import com.android.modules.utils.TypedXmlPullParser;
43 
44 import org.junit.Rule;
45 import org.junit.Test;
46 import org.junit.runner.RunWith;
47 import org.junit.runners.JUnit4;
48 import org.xmlpull.v1.XmlPullParser;
49 
50 import java.io.ByteArrayOutputStream;
51 import java.io.IOException;
52 import java.io.StringReader;
53 import java.io.StringWriter;
54 import java.util.Arrays;
55 import java.util.Base64;
56 import java.util.HashMap;
57 import java.util.Map;
58 import java.util.Set;
59 
60 /**
61  * Unit tests for {@link VibrationXmlParser} and {@link VibrationXmlSerializer}.
62  *
63  * <p>The {@link VibrationEffect} public APIs are covered by CTS to enforce the schema defined at
64  * services/core/xsd/vibrator/vibration/vibration.xsd.
65  */
66 @RunWith(JUnit4.class)
67 public class VibrationEffectXmlSerializationTest {
68 
69     @Rule
70     public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
71 
72     @Test
isSupportedMimeType_onlySupportsVibrationXmlMimeType()73     public void isSupportedMimeType_onlySupportsVibrationXmlMimeType() {
74         // Single MIME type supported
75         assertThat(isSupportedMimeType(
76                 VibrationXmlParser.APPLICATION_VIBRATION_XML_MIME_TYPE)).isTrue();
77         assertThat(isSupportedMimeType("application/vnd.android.haptics.vibration+xml")).isTrue();
78         // without xml suffix not supported
79         assertThat(isSupportedMimeType("application/vnd.android.haptics.vibration")).isFalse();
80         // different top-level not supported
81         assertThat(isSupportedMimeType("haptics/vnd.android.haptics.vibration+xml")).isFalse();
82         // different type not supported
83         assertThat(isSupportedMimeType("application/vnd.android.vibration+xml")).isFalse();
84     }
85 
86     @Test
testParseElement_fromVibrationTag_succeedAndParserPointsToEndVibrationTag()87     public void testParseElement_fromVibrationTag_succeedAndParserPointsToEndVibrationTag()
88             throws Exception {
89         VibrationEffect effect = VibrationEffect.startComposition()
90                 .addPrimitive(PRIMITIVE_CLICK)
91                 .addPrimitive(PRIMITIVE_TICK, 0.2497f)
92                 .compose();
93         String xml = """
94                 <vibration-effect>
95                     <primitive-effect name="click"/>
96                     <primitive-effect name="tick" scale="0.2497"/>
97                 </vibration-effect>
98                 """.trim();
99         VibrationEffect effect2 = VibrationEffect.startComposition()
100                 .addPrimitive(PRIMITIVE_LOW_TICK, 1f, 356)
101                 .addPrimitive(PRIMITIVE_SPIN, 0.6364f, 7)
102                 .compose();
103         String xml2 = """
104                 <vibration-effect>
105                     <primitive-effect name="low_tick" delayMs="356"/>
106                     <primitive-effect name="spin" scale="0.6364" delayMs="7"/>
107                 </vibration-effect>
108                 """.trim();
109 
110         TypedXmlPullParser parser = createXmlPullParser(xml);
111         assertParseElementSucceeds(parser, effect);
112         parser.next();
113         assertEndOfDocument(parser);
114 
115         // Test no-issues when an end-tag follows the vibration XML.
116         // To test this, starting with the corresponding "start-tag" is necessary.
117         parser = createXmlPullParser("<next-tag>" + xml + "</next-tag>");
118         // Move the parser once to point to the "<vibration-effect> tag.
119         parser.next();
120         assertParseElementSucceeds(parser, effect);
121         parser.next();
122         assertEndTag(parser, "next-tag");
123 
124         parser = createXmlPullParser(xml + "<next-tag>");
125         assertParseElementSucceeds(parser, effect);
126         parser.next();
127         assertStartTag(parser, "next-tag");
128 
129         parser = createXmlPullParser(xml + xml2);
130         assertParseElementSucceeds(parser, effect);
131         parser.next();
132         assertParseElementSucceeds(parser, effect2);
133         parser.next();
134         assertEndOfDocument(parser);
135 
136         // Check when there is comment before the end tag.
137         xml = """
138             <vibration-effect>
139                 <primitive-effect name="tick"/>
140                 <!-- hi -->
141             </vibration-effect>
142             """.trim();
143         parser = createXmlPullParser(xml);
144         assertParseElementSucceeds(
145                 parser, VibrationEffect.startComposition().addPrimitive(PRIMITIVE_TICK).compose());
146     }
147 
148     @Test
149     public void
testParseElement_fromVibrationSelectTag_succeedAndParserPointsToEndVibrationSelectTag()150             testParseElement_fromVibrationSelectTag_succeedAndParserPointsToEndVibrationSelectTag()
151                     throws Exception {
152         VibrationEffect effect1 = VibrationEffect.startComposition()
153                 .addPrimitive(PRIMITIVE_CLICK)
154                 .addPrimitive(PRIMITIVE_TICK, 0.2497f)
155                 .compose();
156         String vibrationXml1 = """
157                 <vibration-effect>
158                     <primitive-effect name="click"/>
159                     <primitive-effect name="tick" scale="0.2497"/>
160                 </vibration-effect>
161                 """.trim();
162         VibrationEffect effect2 = VibrationEffect.startComposition()
163                 .addPrimitive(PRIMITIVE_LOW_TICK, 1f, 356)
164                 .addPrimitive(PRIMITIVE_SPIN, 0.6364f, 7)
165                 .compose();
166         String vibrationXml2 = """
167                 <vibration-effect>
168                     <primitive-effect name="low_tick" delayMs="356"/>
169                     <primitive-effect name="spin" scale="0.6364" delayMs="7"/>
170                 </vibration-effect>
171                 """.trim();
172 
173         String xml = "<vibration-select>" + vibrationXml1 + vibrationXml2 + "</vibration-select>";
174         TypedXmlPullParser parser = createXmlPullParser(xml);
175         assertParseElementSucceeds(parser, effect1, effect2);
176         parser.next();
177         assertEndOfDocument(parser);
178 
179         // Test no-issues when an end-tag follows the vibration XML.
180         // To test this, starting with the corresponding "start-tag" is necessary.
181         parser = createXmlPullParser("<next-tag>" + xml + "</next-tag>");
182         // Move the parser once to point to the "<vibration-effect> tag.
183         parser.next();
184         assertParseElementSucceeds(parser, effect1, effect2);
185         parser.next();
186         assertEndTag(parser, "next-tag");
187 
188         parser = createXmlPullParser(xml + "<next-tag>");
189         assertParseElementSucceeds(parser, effect1, effect2);
190         parser.next();
191         assertStartTag(parser, "next-tag");
192 
193         xml = "<vibration-select>" + vibrationXml1 + vibrationXml2 + "</vibration-select>"
194                 + "<vibration-select>" + vibrationXml2 + vibrationXml1 + "</vibration-select>"
195                 + vibrationXml1;
196         parser = createXmlPullParser(xml);
197         assertParseElementSucceeds(parser, effect1, effect2);
198         parser.next();
199         assertParseElementSucceeds(parser, effect2, effect1);
200         parser.next();
201         assertParseElementSucceeds(parser, effect1);
202         parser.next();
203         assertEndOfDocument(parser);
204 
205         // Check when there is comment before the end tag.
206         xml = "<vibration-select>" + vibrationXml1 + "<!-- comment --></vibration-select>";
207         parser = createXmlPullParser(xml);
208         parser.next();
209         assertParseElementSucceeds(parser, effect1);
210     }
211 
212     @Test
testParseElement_withHiddenApis_onlySucceedsWithFlag()213     public void testParseElement_withHiddenApis_onlySucceedsWithFlag() throws Exception {
214         // Check when the root tag is "vibration".
215         String xml = """
216                 <vibration-effect>
217                     <predefined-effect name="texture_tick"/>
218                 </vibration-effect>
219                 """.trim();
220         assertParseElementSucceeds(createXmlPullParser(xml),
221                 VibrationXmlSerializer.FLAG_ALLOW_HIDDEN_APIS,
222                 VibrationEffect.get(VibrationEffect.EFFECT_TEXTURE_TICK));
223         assertParseElementFails(xml);
224 
225         // Check when the root tag is "vibration-select".
226         xml = "<vibration-select>" + xml + "</vibration-select>";
227         assertParseElementSucceeds(createXmlPullParser(xml),
228                 VibrationXmlSerializer.FLAG_ALLOW_HIDDEN_APIS,
229                 VibrationEffect.get(VibrationEffect.EFFECT_TEXTURE_TICK));
230         assertParseElementFails(xml);
231     }
232 
233     @Test
testParseElement_badXml_throwsException()234     public void testParseElement_badXml_throwsException() {
235         // No "vibration-select" tag.
236         assertParseElementFails("""
237                 <vibration-effect>
238                     rand text
239                     <primitive-effect name="click"/>
240                 </vibration-effect>
241                 """);
242         assertParseElementFails("""
243                 <bad-tag>
244                     <primitive-effect name="click"/>
245                 </vibration-effect>
246                 """);
247         assertParseElementFails("""
248                 <primitive-effect name="click"/>
249                 </vibration-effect>
250                 """);
251         assertParseElementFails("""
252                 <vibration-effect>
253                     <primitive-effect name="click"/>
254                 """);
255 
256         // Incomplete XML.
257         assertParseElementFails("""
258                 <vibration-select>
259                     <primitive-effect name="click"/>
260                 """);
261         assertParseElementFails("""
262                 <vibration-select>
263                     <vibration-effect>
264                         <primitive-effect name="low_tick" delayMs="356"/>
265                     </vibration-effect>
266                 """);
267 
268         // Bad vibration XML.
269         assertParseElementFails("""
270                 <vibration-select>
271                     <primitive-effect name="low_tick" delayMs="356"/>
272                     </vibration-effect>
273                 </vibration-select>
274                 """);
275 
276         // "vibration-select" tag should have no attributes.
277         assertParseElementFails("""
278                 <vibration-select bad_attr="123">
279                     <vibration-effect>
280                         <predefined-effect name="tick"/>
281                     </vibration-effect>
282                 </vibration-select>
283                 """);
284     }
285 
286     @Test
testInvalidEffects_allFail()287     public void testInvalidEffects_allFail() {
288         // Invalid root tag.
289         String xml = """
290                 <vibration>
291                     <predefined-effect name="click"/>
292                 </vibration>
293                 """;
294 
295         assertPublicApisParserFails(xml);
296         assertHiddenApisParserFails(xml);
297 
298         // Invalid effect name.
299         xml = """
300                 <vibration-effect>
301                     <predefined-effect name="invalid"/>
302                 </vibration-effect>
303                 """;
304 
305         assertPublicApisParserFails(xml);
306         assertHiddenApisParserFails(xml);
307     }
308 
309     @Test
testVibrationSelectTag_onlyParseDocumentSucceeds()310     public void testVibrationSelectTag_onlyParseDocumentSucceeds() throws Exception {
311         VibrationEffect effect = VibrationEffect.get(VibrationEffect.EFFECT_CLICK);
312         String xml = """
313                 <vibration-select>
314                     <vibration-effect><predefined-effect name="click"/></vibration-effect>
315                 </vibration-select>
316                 """;
317 
318         assertPublicApisParseDocumentSucceeds(xml, effect);
319         assertHiddenApisParseDocumentSucceeds(xml, effect);
320 
321         assertPublicApisParseVibrationEffectFails(xml);
322         assertHiddenApisParseVibrationEffectFails(xml);
323     }
324 
325     @Test
testPrimitives_allSucceed()326     public void testPrimitives_allSucceed() throws Exception {
327         VibrationEffect effect = VibrationEffect.startComposition()
328                 .addPrimitive(PRIMITIVE_CLICK)
329                 .addPrimitive(PRIMITIVE_TICK, 0.2497f)
330                 .addPrimitive(PRIMITIVE_LOW_TICK, 1f, 356)
331                 .addPrimitive(PRIMITIVE_SPIN, 0.6364f, 7)
332                 .compose();
333         String xml = """
334                 <vibration-effect>
335                     <primitive-effect name="click"/>
336                     <primitive-effect name="tick" scale="0.2497"/>
337                     <primitive-effect name="low_tick" delayMs="356"/>
338                     <primitive-effect name="spin" scale="0.6364" delayMs="7"/>
339                 </vibration-effect>
340                 """;
341 
342         assertPublicApisParserSucceeds(xml, effect);
343         assertPublicApisSerializerSucceeds(effect, "click", "tick", "low_tick", "spin");
344         assertPublicApisRoundTrip(effect);
345 
346         assertHiddenApisParserSucceeds(xml, effect);
347         assertHiddenApisSerializerSucceeds(effect, "click", "tick", "low_tick", "spin");
348         assertHiddenApisRoundTrip(effect);
349     }
350 
351     @Test
testWaveforms_allSucceed()352     public void testWaveforms_allSucceed() throws Exception {
353         VibrationEffect effect = VibrationEffect.createWaveform(new long[]{123, 456, 789, 0},
354                 new int[]{254, 1, 255, 0}, /* repeat= */ 0);
355         String xml = """
356                 <vibration-effect>
357                     <waveform-effect>
358                         <repeating>
359                             <waveform-entry durationMs="123" amplitude="254"/>
360                             <waveform-entry durationMs="456" amplitude="1"/>
361                             <waveform-entry durationMs="789" amplitude="255"/>
362                             <waveform-entry durationMs="0" amplitude="0"/>
363                         </repeating>
364                     </waveform-effect>
365                 </vibration-effect>
366                 """;
367 
368         assertPublicApisParserSucceeds(xml, effect);
369         assertPublicApisSerializerSucceeds(effect, "123", "456", "789", "254", "1", "255", "0");
370         assertPublicApisRoundTrip(effect);
371 
372         assertHiddenApisParserSucceeds(xml, effect);
373         assertHiddenApisSerializerSucceeds(effect, "123", "456", "789", "254", "1", "255", "0");
374         assertHiddenApisRoundTrip(effect);
375     }
376 
377     @Test
testPredefinedEffects_publicEffectsWithDefaultFallback_allSucceed()378     public void testPredefinedEffects_publicEffectsWithDefaultFallback_allSucceed()
379             throws Exception {
380         for (Map.Entry<String, Integer> entry : createPublicPredefinedEffectsMap().entrySet()) {
381             VibrationEffect effect = VibrationEffect.get(entry.getValue());
382             String xml = String.format("""
383                     <vibration-effect>
384                         <predefined-effect name="%s"/>
385                     </vibration-effect>
386                     """,
387                     entry.getKey());
388 
389             assertPublicApisParserSucceeds(xml, effect);
390             assertPublicApisSerializerSucceeds(effect, entry.getKey());
391             assertPublicApisRoundTrip(effect);
392 
393             assertHiddenApisParserSucceeds(xml, effect);
394             assertHiddenApisSerializerSucceeds(effect, entry.getKey());
395             assertHiddenApisRoundTrip(effect);
396         }
397     }
398 
399     @Test
testPredefinedEffects_hiddenEffects_onlySucceedsWithFlag()400     public void testPredefinedEffects_hiddenEffects_onlySucceedsWithFlag() throws Exception {
401         for (Map.Entry<String, Integer> entry : createHiddenPredefinedEffectsMap().entrySet()) {
402             VibrationEffect effect = VibrationEffect.get(entry.getValue());
403             String xml = String.format("""
404                     <vibration-effect>
405                         <predefined-effect name="%s"/>
406                     </vibration-effect>
407                     """,
408                     entry.getKey());
409 
410             assertPublicApisParserFails(xml);
411             assertPublicApisSerializerFails(effect);
412 
413             assertHiddenApisParserSucceeds(xml, effect);
414             assertHiddenApisSerializerSucceeds(effect, entry.getKey());
415             assertHiddenApisRoundTrip(effect);
416         }
417     }
418 
419     @Test
testPredefinedEffects_allEffectsWithNonDefaultFallback_onlySucceedsWithFlag()420     public void testPredefinedEffects_allEffectsWithNonDefaultFallback_onlySucceedsWithFlag()
421             throws Exception {
422         for (Map.Entry<String, Integer> entry : createAllPredefinedEffectsMap().entrySet()) {
423             boolean nonDefaultFallback = !PrebakedSegment.DEFAULT_SHOULD_FALLBACK;
424             VibrationEffect effect = VibrationEffect.get(entry.getValue(), nonDefaultFallback);
425             String xml = String.format("""
426                     <vibration-effect>
427                         <predefined-effect name="%s" fallback="%s"/>
428                     </vibration-effect>
429                     """,
430                     entry.getKey(), nonDefaultFallback);
431 
432             assertPublicApisParserFails(xml);
433             assertPublicApisSerializerFails(effect);
434 
435             assertHiddenApisParserSucceeds(xml, effect);
436             assertHiddenApisSerializerSucceeds(effect, entry.getKey());
437             assertHiddenApisRoundTrip(effect);
438         }
439     }
440 
441     @Test
442     @EnableFlags(Flags.FLAG_NORMALIZED_PWLE_EFFECTS)
testWaveformEnvelopeEffect_allSucceed()443     public void testWaveformEnvelopeEffect_allSucceed() throws Exception {
444         VibrationEffect effect = new VibrationEffect.WaveformEnvelopeBuilder()
445                 .addControlPoint(0.2f, 80f, 10)
446                 .addControlPoint(0.5f, 150f, 10)
447                 .build();
448 
449         String xml = """
450                 <vibration-effect>
451                     <waveform-envelope-effect>
452                         <control-point amplitude="0.2" frequencyHz="80.0" durationMs="10" />
453                         <control-point amplitude="0.5" frequencyHz="150.0" durationMs="10" />
454                     </waveform-envelope-effect>
455                 </vibration-effect>
456                 """;
457 
458         assertPublicApisParserSucceeds(xml, effect);
459         assertPublicApisSerializerSucceeds(effect, xml);
460         assertPublicApisRoundTrip(effect);
461         assertHiddenApisParserSucceeds(xml, effect);
462         assertHiddenApisSerializerSucceeds(effect, xml);
463         assertHiddenApisRoundTrip(effect);
464     }
465 
466     @Test
467     @EnableFlags(Flags.FLAG_NORMALIZED_PWLE_EFFECTS)
testWaveformEnvelopeEffectWithInitialFrequency_allSucceed()468     public void testWaveformEnvelopeEffectWithInitialFrequency_allSucceed() throws Exception {
469         VibrationEffect effect = new VibrationEffect.WaveformEnvelopeBuilder()
470                 .setInitialFrequencyHz(20)
471                 .addControlPoint(0.2f, 80f, 10)
472                 .addControlPoint(0.5f, 150f, 10)
473                 .build();
474 
475         String xml = """
476                 <vibration-effect>
477                     <waveform-envelope-effect initialFrequencyHz="20.0">
478                         <control-point amplitude="0.2" frequencyHz="80.0" durationMs="10" />
479                         <control-point amplitude="0.5" frequencyHz="150.0" durationMs="10" />
480                     </waveform-envelope-effect>
481                 </vibration-effect>
482                 """;
483 
484         assertPublicApisParserSucceeds(xml, effect);
485         assertPublicApisSerializerSucceeds(effect, xml);
486         assertPublicApisRoundTrip(effect);
487         assertHiddenApisParserSucceeds(xml, effect);
488         assertHiddenApisSerializerSucceeds(effect, xml);
489         assertHiddenApisRoundTrip(effect);
490     }
491 
492     @Test
493     @EnableFlags(Flags.FLAG_NORMALIZED_PWLE_EFFECTS)
testWaveformEnvelopeEffect_badXml_throwsException()494     public void testWaveformEnvelopeEffect_badXml_throwsException() throws IOException {
495         // Incomplete XML
496         assertParseElementFails("""
497                 <vibration-effect>
498                     <waveform-envelope-effect>
499                         <control-point amplitude="0.2" frequencyHz="80.0" durationMs="10" />
500                 </vibration-effect>
501                 """);
502         assertParseElementFails("""
503                 <vibration-effect>
504                     <waveform-envelope-effect>
505                         <control-point amplitude="0.2" frequencyHz="80.0" durationMs="10">
506                     </waveform-envelope-effect>
507                 </vibration-effect>
508                 """);
509         assertParseElementFails("""
510                 <vibration-effect>
511                     <waveform-envelope-effect>
512                         <control-point amplitude="0.2" frequencyHz="80.0" durationMs="10" />
513                     </waveform-envelope-effect>
514                 """);
515 
516         // Bad vibration XML
517         assertParseElementFails("""
518                 <vibration-effect>
519                         <control-point amplitude="0.2" frequencyHz="80.0" durationMs="10" />
520                     </waveform-envelope-effect>
521                 </vibration-effect>
522                 """);
523 
524         // "waveform-envelope-effect" tag with invalid attributes
525         assertParseElementFails("""
526                 <vibration-effect>
527                     <waveform-envelope-effect init_freq="20.0">
528                         <control-point amplitude="0.2" frequencyHz="80.0" durationMs="10" />
529                     </waveform-envelope-effect>
530                 </vibration-effect>
531                 """);
532     }
533 
534     @Test
535     @EnableFlags(Flags.FLAG_NORMALIZED_PWLE_EFFECTS)
testWaveformEnvelopeEffect_noControlPoints_allFail()536     public void testWaveformEnvelopeEffect_noControlPoints_allFail() throws IOException {
537         String xml = "<vibration-effect><waveform-envelope-effect/></vibration-effect>";
538         assertPublicApisParserFails(xml);
539         assertHiddenApisParserFails(xml);
540 
541         xml = "<vibration-effect><waveform-envelope-effect> \n "
542                 + "</waveform-envelope-effect></vibration-effect>";
543         assertPublicApisParserFails(xml);
544         assertHiddenApisParserFails(xml);
545 
546         xml = "<vibration-effect><waveform-envelope-effect>invalid</waveform-envelope-effect"
547                 + "></vibration-effect>";
548         assertPublicApisParserFails(xml);
549         assertHiddenApisParserFails(xml);
550 
551         xml = """
552                 <vibration-effect>
553                     <waveform-envelope-effect>
554                     <control-point />
555                     </waveform-envelope-effect>
556                 </vibration-effect>
557                 """;
558         assertPublicApisParserFails(xml);
559         assertHiddenApisParserFails(xml);
560 
561         xml = """
562                 <vibration-effect>
563                     <waveform-envelope-effect initialFrequencyHz="20.0" />
564                 </vibration-effect>
565                 """;
566         assertPublicApisParserFails(xml);
567         assertHiddenApisParserFails(xml);
568 
569         xml = """
570                 <vibration-effect>
571                 <waveform-envelope-effect initialFrequencyHz="20.0"> \n </waveform-envelope-effect>
572                 </vibration-effect>
573                 """;
574         assertPublicApisParserFails(xml);
575         assertHiddenApisParserFails(xml);
576 
577         xml = """
578                 <vibration-effect>
579                     <waveform-envelope-effect initialFrequencyHz="20.0">
580                     invalid
581                     </waveform-envelope-effect>
582                 </vibration-effect>
583                 """;
584         assertPublicApisParserFails(xml);
585         assertHiddenApisParserFails(xml);
586 
587         xml = """
588                 <vibration-effect>
589                     <waveform-envelope-effect initialFrequencyHz="20.0">
590                     <control-point />
591                     </waveform-envelope-effect>
592                 </vibration-effect>
593                 """;
594         assertPublicApisParserFails(xml);
595         assertHiddenApisParserFails(xml);
596     }
597 
598     @Test
599     @EnableFlags(Flags.FLAG_NORMALIZED_PWLE_EFFECTS)
testWaveformEnvelopeEffect_badControlPointData_allFail()600     public void testWaveformEnvelopeEffect_badControlPointData_allFail() throws IOException {
601         String xml = """
602                 <vibration-effect>
603                     <waveform-envelope-effect>
604                     <control-point amplitude="-1" frequencyHz="80.0" durationMs="100" />
605                     </waveform-envelope-effect>
606                 </vibration-effect>
607                 """;
608         assertPublicApisParserFails(xml);
609         assertHiddenApisParserFails(xml);
610 
611         xml = """
612                 <vibration-effect>
613                     <waveform-envelope-effect>
614                     <control-point amplitude="0.2" frequencyHz="0" durationMs="100" />
615                     </waveform-envelope-effect>
616                 </vibration-effect>
617                 """;
618         assertPublicApisParserFails(xml);
619         assertHiddenApisParserFails(xml);
620 
621         xml = """
622                 <vibration-effect>
623                     <waveform-envelope-effect initialFrequencyHz="0">
624                     <control-point amplitude="0.2" frequencyHz="30" durationMs="100" />
625                     </waveform-envelope-effect>
626                 </vibration-effect>
627                 """;
628         assertPublicApisParserFails(xml);
629         assertHiddenApisParserFails(xml);
630 
631         xml = """
632                 <vibration-effect>
633                     <waveform-envelope-effect>
634                     <control-point amplitude="0.2" frequencyHz="80.0" durationMs="0" />
635                     </waveform-envelope-effect>
636                 </vibration-effect>
637                 """;
638         assertPublicApisParserFails(xml);
639         assertHiddenApisParserFails(xml);
640 
641         xml = """
642                 <vibration-effect>
643                     <waveform-envelope-effect>
644                     <control-point amplitude="0.2" />
645                     </waveform-envelope-effect>
646                 </vibration-effect>
647                 """;
648         assertPublicApisParserFails(xml);
649         assertHiddenApisParserFails(xml);
650     }
651 
652     @Test
653     @DisableFlags(Flags.FLAG_NORMALIZED_PWLE_EFFECTS)
testWaveformEnvelopeEffect_featureFlagDisabled_allFail()654     public void testWaveformEnvelopeEffect_featureFlagDisabled_allFail() throws Exception {
655         VibrationEffect effect = new VibrationEffect.WaveformEnvelopeBuilder()
656                 .setInitialFrequencyHz(20)
657                 .addControlPoint(0.2f, 80f, 10)
658                 .addControlPoint(0.5f, 150f, 10)
659                 .build();
660 
661         String xml = """
662                 <vibration-effect>
663                     <waveform-envelope-effect initialFrequencyHz="20.0">
664                         <control-point amplitude="0.2" frequencyHz="80.0" durationMs="10" />
665                         <control-point amplitude="0.5" frequencyHz="150.0" durationMs="10" />
666                     </waveform-envelope-effect>
667                 </vibration-effect>
668                 """;
669 
670         assertPublicApisParserFails(xml);
671         assertPublicApisSerializerFails(effect);
672         assertHiddenApisParserFails(xml);
673         assertHiddenApisSerializerFails(effect);
674     }
675 
676     @Test
677     @EnableFlags(Flags.FLAG_NORMALIZED_PWLE_EFFECTS)
testBasicEnvelopeEffect_allSucceed()678     public void testBasicEnvelopeEffect_allSucceed() throws Exception {
679         VibrationEffect effect = new VibrationEffect.BasicEnvelopeBuilder()
680                 .addControlPoint(0.2f, 0.5f, 10)
681                 .addControlPoint(0.0f, 1f, 10)
682                 .build();
683 
684         String xml = """
685                 <vibration-effect>
686                     <basic-envelope-effect>
687                         <control-point intensity="0.2" sharpness="0.5" durationMs="10" />
688                         <control-point intensity="0.0" sharpness="1.0" durationMs="10" />
689                     </basic-envelope-effect>
690                 </vibration-effect>
691                 """;
692 
693         assertPublicApisParserSucceeds(xml, effect);
694         assertPublicApisSerializerSucceeds(effect, xml);
695         assertPublicApisRoundTrip(effect);
696         assertHiddenApisParserSucceeds(xml, effect);
697         assertHiddenApisSerializerSucceeds(effect, xml);
698         assertHiddenApisRoundTrip(effect);
699     }
700 
701     @Test
702     @EnableFlags(Flags.FLAG_NORMALIZED_PWLE_EFFECTS)
testBasicEnvelopeEffectWithInitialSharpness_allSucceed()703     public void testBasicEnvelopeEffectWithInitialSharpness_allSucceed() throws Exception {
704         VibrationEffect effect = new VibrationEffect.BasicEnvelopeBuilder()
705                 .setInitialSharpness(0.3f)
706                 .addControlPoint(0.2f, 0.5f, 10)
707                 .addControlPoint(0.0f, 1f, 10)
708                 .build();
709 
710         String xml = """
711                 <vibration-effect>
712                     <basic-envelope-effect initialSharpness="0.3">
713                         <control-point intensity="0.2" sharpness="0.5" durationMs="10" />
714                         <control-point intensity="0.0" sharpness="1.0" durationMs="10" />
715                     </basic-envelope-effect>
716                 </vibration-effect>
717                 """;
718 
719         assertPublicApisParserSucceeds(xml, effect);
720         assertPublicApisSerializerSucceeds(effect, xml);
721         assertPublicApisRoundTrip(effect);
722         assertHiddenApisParserSucceeds(xml, effect);
723         assertHiddenApisSerializerSucceeds(effect, xml);
724         assertHiddenApisRoundTrip(effect);
725     }
726 
727     @Test
728     @EnableFlags(Flags.FLAG_NORMALIZED_PWLE_EFFECTS)
testBasicEnvelopeEffect_badXml_throwsException()729     public void testBasicEnvelopeEffect_badXml_throwsException() throws IOException {
730         // Incomplete XML
731         assertParseElementFails("""
732                 <vibration-effect>
733                     <basic-envelope-effect>
734                         <control-point intensity="0.2" sharpness="0.8" durationMs="10" />
735                         <control-point intensity="0.0" sharpness="1.0" durationMs="10" />
736                 </vibration-effect>
737                 """);
738         assertParseElementFails("""
739                 <vibration-effect>
740                     <basic-envelope-effect>
741                         <control-point intensity="0.2" sharpness="0.8" durationMs="10">
742                         <control-point intensity="0.0" sharpness="1.0" durationMs="10" />
743                     </basic-envelope-effect>
744                 </vibration-effect>
745                 """);
746         assertParseElementFails("""
747                 <vibration-effect>
748                     <basic-envelope-effect>
749                         <control-point intensity="0.2" sharpness="0.8" durationMs="10" />
750                         <control-point intensity="0.0" sharpness="1.0" durationMs="10" />
751                     </basic-envelope-effect>
752                 """);
753 
754         // Bad vibration XML
755         assertParseElementFails("""
756                 <vibration-effect>
757                         <control-point intensity="0.2" sharpness="0.8" durationMs="10" />
758                         <control-point intensity="0.0" sharpness="1.0" durationMs="10" />
759                     </basic-envelope-effect>
760                 </vibration-effect>
761                 """);
762 
763         // "basic-envelope-effect" tag with invalid attributes
764         assertParseElementFails("""
765                 <vibration-effect>
766                     <basic-envelope-effect init_sharp="20.0">
767                         <control-point intensity="0.2" sharpness="0.8" durationMs="10" />
768                         <control-point intensity="0.0" sharpness="1.0" durationMs="10" />
769                     </basic-envelope-effect>
770                 </vibration-effect>
771                 """);
772     }
773 
774     @Test
775     @EnableFlags(Flags.FLAG_NORMALIZED_PWLE_EFFECTS)
testBasicEnvelopeEffect_noControlPoints_allFail()776     public void testBasicEnvelopeEffect_noControlPoints_allFail() throws IOException {
777         String xml = "<vibration-effect><basic-envelope-effect/></vibration-effect>";
778         assertPublicApisParserFails(xml);
779         assertHiddenApisParserFails(xml);
780 
781         xml = "<vibration-effect><basic-envelope-effect> \n "
782                 + "</basic-envelope-effect></vibration-effect>";
783         assertPublicApisParserFails(xml);
784         assertHiddenApisParserFails(xml);
785 
786         xml = "<vibration-effect><basic-envelope-effect>invalid</basic-envelope-effect"
787                 + "></vibration-effect>";
788         assertPublicApisParserFails(xml);
789         assertHiddenApisParserFails(xml);
790 
791         xml = """
792                 <vibration-effect>
793                     <basic-envelope-effect>
794                     <control-point />
795                     </basic-envelope-effect>
796                 </vibration-effect>
797                 """;
798         assertPublicApisParserFails(xml);
799         assertHiddenApisParserFails(xml);
800 
801         xml = """
802                 <vibration-effect>
803                     <basic-envelope-effect initialSharpness="0.2" />
804                 </vibration-effect>
805                 """;
806         assertPublicApisParserFails(xml);
807         assertHiddenApisParserFails(xml);
808 
809         xml = """
810                 <vibration-effect>
811                 <basic-envelope-effect initialSharpness="0.2"> \n </basic-envelope-effect>
812                 </vibration-effect>
813                 """;
814         assertPublicApisParserFails(xml);
815         assertHiddenApisParserFails(xml);
816 
817         xml = """
818                 <vibration-effect>
819                     <basic-envelope-effect initialSharpness="0.2">
820                     invalid
821                     </basic-envelope-effect>
822                 </vibration-effect>
823                 """;
824         assertPublicApisParserFails(xml);
825         assertHiddenApisParserFails(xml);
826 
827         xml = """
828                 <vibration-effect>
829                     <basic-envelope-effect initialSharpness="0.2">
830                     <control-point />
831                     </basic-envelope-effect>
832                 </vibration-effect>
833                 """;
834         assertPublicApisParserFails(xml);
835         assertHiddenApisParserFails(xml);
836     }
837 
838     @Test
839     @EnableFlags(Flags.FLAG_NORMALIZED_PWLE_EFFECTS)
testBasicEnvelopeEffect_badControlPointData_allFail()840     public void testBasicEnvelopeEffect_badControlPointData_allFail() throws IOException {
841         String xml = """
842                 <vibration-effect>
843                     <basic-envelope-effect>
844                     <control-point intensity="-1" sharpness="0.8" durationMs="100" />
845                     <control-point intensity="0.0" sharpness="1.0" durationMs="10" />
846                     </basic-envelope-effect>
847                 </vibration-effect>
848                 """;
849         assertPublicApisParserFails(xml);
850         assertHiddenApisParserFails(xml);
851 
852         xml = """
853                 <vibration-effect>
854                     <basic-envelope-effect>
855                     <control-point intensity="0.2" sharpness="-1" durationMs="100" />
856                     <control-point intensity="0.0" sharpness="1.0" durationMs="10" />
857                     </basic-envelope-effect>
858                 </vibration-effect>
859                 """;
860         assertPublicApisParserFails(xml);
861         assertHiddenApisParserFails(xml);
862 
863         xml = """
864                 <vibration-effect>
865                     <basic-envelope-effect initialSharpness="-1.0">
866                     <control-point intensity="0.2" sharpness="0.8" durationMs="0" />
867                     <control-point intensity="0.0" sharpness="1.0" durationMs="10" />
868                     </basic-envelope-effect>
869                 </vibration-effect>
870                 """;
871         assertPublicApisParserFails(xml);
872         assertHiddenApisParserFails(xml);
873 
874         xml = """
875                 <vibration-effect>
876                     <basic-envelope-effect initialSharpness="2.0">
877                     <control-point intensity="0.2" sharpness="0.8" durationMs="0" />
878                     <control-point intensity="0.0" sharpness="1.0" durationMs="10" />
879                     </basic-envelope-effect>
880                 </vibration-effect>
881                 """;
882         assertPublicApisParserFails(xml);
883         assertHiddenApisParserFails(xml);
884 
885         xml = """
886                 <vibration-effect>
887                     <basic-envelope-effect>
888                     <control-point intensity="0.2" sharpness="0.8" durationMs="10" />
889                     <control-point intensity="0.5" sharpness="0.8" durationMs="10" />
890                     </basic-envelope-effect>
891                 </vibration-effect>
892                 """;
893         assertPublicApisParserFails(xml);
894         assertHiddenApisParserFails(xml);
895 
896         xml = """
897                 <vibration-effect>
898                     <basic-envelope-effect>
899                     <control-point intensity="0.2" />
900                     <control-point intensity="0.0" sharpness="1.0" durationMs="10" />
901                     </basic-envelope-effect>
902                 </vibration-effect>
903                 """;
904         assertPublicApisParserFails(xml);
905         assertHiddenApisParserFails(xml);
906     }
907 
908     @Test
909     @DisableFlags(Flags.FLAG_NORMALIZED_PWLE_EFFECTS)
testBasicEnvelopeEffect_featureFlagDisabled_allFail()910     public void testBasicEnvelopeEffect_featureFlagDisabled_allFail() throws Exception {
911         VibrationEffect effect = new VibrationEffect.BasicEnvelopeBuilder()
912                 .setInitialSharpness(0.3f)
913                 .addControlPoint(0.2f, 0.5f, 10)
914                 .addControlPoint(0.0f, 1f, 10)
915                 .build();
916 
917         String xml = """
918                 <vibration-effect>
919                     <basic-envelope-effect initialSharpness="0.3">
920                         <control-point intensity="0.2" sharpness="0.5" durationMs="10" />
921                         <control-point intensity="0.0" sharpness="1.0" durationMs="10" />
922                     </basic-envelope-effect>
923                 </vibration-effect>
924                 """;
925 
926         assertPublicApisParserFails(xml);
927         assertPublicApisSerializerFails(effect);
928 
929         assertHiddenApisParserFails(xml);
930         assertHiddenApisSerializerFails(effect);
931     }
932 
933     @Test
934     @EnableFlags(Flags.FLAG_NORMALIZED_PWLE_EFFECTS)
testRepeating_withWaveformEnvelopeEffect_allSucceed()935     public void testRepeating_withWaveformEnvelopeEffect_allSucceed() throws Exception {
936         VibrationEffect preamble = new VibrationEffect.WaveformEnvelopeBuilder()
937                 .addControlPoint(0.1f, 50f, 10)
938                 .addControlPoint(0.2f, 60f, 20)
939                 .build();
940         VibrationEffect repeating = new VibrationEffect.WaveformEnvelopeBuilder()
941                 .setInitialFrequencyHz(70f)
942                 .addControlPoint(0.3f, 80f, 25)
943                 .addControlPoint(0.4f, 90f, 30)
944                 .build();
945         VibrationEffect effect = VibrationEffect.createRepeatingEffect(preamble, repeating);
946 
947         String xml = """
948                 <vibration-effect>
949                 <repeating-effect>
950                     <preamble>
951                         <waveform-envelope-effect>
952                             <control-point amplitude="0.1" frequencyHz="50.0" durationMs="10"/>
953                             <control-point amplitude="0.2" frequencyHz="60.0" durationMs="20"/>
954                         </waveform-envelope-effect>
955                     </preamble>
956                     <repeating>
957                         <waveform-envelope-effect initialFrequencyHz="70.0">
958                             <control-point amplitude="0.3" frequencyHz="80.0" durationMs="25"/>
959                             <control-point amplitude="0.4" frequencyHz="90.0" durationMs="30"/>
960                         </waveform-envelope-effect>
961                     </repeating>
962                 </repeating-effect>
963                 </vibration-effect>
964                 """;
965 
966         assertPublicApisParserSucceeds(xml, effect);
967         assertPublicApisSerializerSucceeds(effect, "0.1", "0.2", "0.3", "0.4", "50.0", "60.0",
968                 "70.0", "80.0", "90.0", "10", "20", "25", "30");
969         assertPublicApisRoundTrip(effect);
970 
971         assertHiddenApisParserSucceeds(xml, effect);
972         assertHiddenApisSerializerSucceeds(effect, "0.1", "0.2", "0.3", "0.4", "50.0", "60.0",
973                 "70.0", "80.0", "90.0", "10", "20", "25", "30");
974         assertHiddenApisRoundTrip(effect);
975 
976         effect = VibrationEffect.createRepeatingEffect(repeating);
977 
978         xml = """
979                 <vibration-effect>
980                 <repeating-effect>
981                     <repeating>
982                         <waveform-envelope-effect initialFrequencyHz="70.0">
983                             <control-point amplitude="0.3" frequencyHz="80.0" durationMs="25"/>
984                             <control-point amplitude="0.4" frequencyHz="90.0" durationMs="30"/>
985                         </waveform-envelope-effect>
986                     </repeating>
987                 </repeating-effect>
988                 </vibration-effect>
989                 """;
990 
991         assertPublicApisParserSucceeds(xml, effect);
992         assertPublicApisSerializerSucceeds(effect, "0.3", "0.4", "70.0", "80.0", "90.0", "25",
993                 "30");
994         assertPublicApisRoundTrip(effect);
995 
996         assertHiddenApisParserSucceeds(xml, effect);
997         assertHiddenApisSerializerSucceeds(effect, "0.3", "0.4", "70.0", "80.0", "90.0", "25",
998                 "30");
999         assertHiddenApisRoundTrip(effect);
1000     }
1001 
1002     @Test
1003     @EnableFlags(Flags.FLAG_NORMALIZED_PWLE_EFFECTS)
testRepeating_withBasicEnvelopeEffect_allSucceed()1004     public void testRepeating_withBasicEnvelopeEffect_allSucceed() throws Exception {
1005         VibrationEffect preamble = new VibrationEffect.BasicEnvelopeBuilder()
1006                 .addControlPoint(0.1f, 0.1f, 10)
1007                 .addControlPoint(0.2f, 0.2f, 20)
1008                 .addControlPoint(0.0f, 0.2f, 20)
1009                 .build();
1010         VibrationEffect repeating = new VibrationEffect.BasicEnvelopeBuilder()
1011                 .setInitialSharpness(0.3f)
1012                 .addControlPoint(0.3f, 0.4f, 25)
1013                 .addControlPoint(0.4f, 0.6f, 30)
1014                 .addControlPoint(0.0f, 0.7f, 35)
1015                 .build();
1016         VibrationEffect effect = VibrationEffect.createRepeatingEffect(preamble, repeating);
1017 
1018         String xml = """
1019                 <vibration-effect>
1020                 <repeating-effect>
1021                     <preamble>
1022                         <basic-envelope-effect>
1023                             <control-point intensity="0.1" sharpness="0.1" durationMs="10" />
1024                             <control-point intensity="0.2" sharpness="0.2" durationMs="20" />
1025                             <control-point intensity="0.0" sharpness="0.2" durationMs="20" />
1026                         </basic-envelope-effect>
1027                     </preamble>
1028                     <repeating>
1029                         <basic-envelope-effect initialSharpness="0.3">
1030                             <control-point intensity="0.3" sharpness="0.4" durationMs="25" />
1031                             <control-point intensity="0.4" sharpness="0.6" durationMs="30" />
1032                             <control-point intensity="0.0" sharpness="0.7" durationMs="35" />
1033                         </basic-envelope-effect>
1034                     </repeating>
1035                 </repeating-effect>
1036                 </vibration-effect>
1037                 """;
1038 
1039         assertPublicApisParserSucceeds(xml, effect);
1040         assertPublicApisSerializerSucceeds(effect, "0.0", "0.1", "0.2", "0.3", "0.4", "0.1", "0.2",
1041                 "0.3", "0.4", "0.6", "0.7", "10", "20", "25", "30", "35");
1042         assertPublicApisRoundTrip(effect);
1043 
1044         assertHiddenApisParserSucceeds(xml, effect);
1045         assertHiddenApisSerializerSucceeds(effect, "0.0", "0.1", "0.2", "0.3", "0.4", "0.1", "0.2",
1046                 "0.3", "0.4", "0.6", "0.7", "10", "20", "25", "30", "35");
1047         assertHiddenApisRoundTrip(effect);
1048 
1049         effect = VibrationEffect.createRepeatingEffect(repeating);
1050 
1051         xml = """
1052                 <vibration-effect>
1053                 <repeating-effect>
1054                     <repeating>
1055                         <basic-envelope-effect initialSharpness="0.3">
1056                             <control-point intensity="0.3" sharpness="0.4" durationMs="25" />
1057                             <control-point intensity="0.4" sharpness="0.6" durationMs="30" />
1058                             <control-point intensity="0.0" sharpness="0.7" durationMs="35" />
1059                         </basic-envelope-effect>
1060                     </repeating>
1061                 </repeating-effect>
1062                 </vibration-effect>
1063                 """;
1064 
1065         assertPublicApisParserSucceeds(xml, effect);
1066         assertPublicApisSerializerSucceeds(effect, "0.3", "0.4", "0.0", "0.4", "0.6", "0.7", "25",
1067                 "30", "35");
1068         assertPublicApisRoundTrip(effect);
1069 
1070         assertHiddenApisParserSucceeds(xml, effect);
1071         assertHiddenApisSerializerSucceeds(effect, "0.3", "0.4", "0.0", "0.4", "0.6", "0.7", "25",
1072                 "30", "35");
1073         assertHiddenApisRoundTrip(effect);
1074     }
1075 
1076     @Test
1077     @EnableFlags(Flags.FLAG_NORMALIZED_PWLE_EFFECTS)
testRepeating_withPredefinedEffects_allSucceed()1078     public void testRepeating_withPredefinedEffects_allSucceed() throws Exception {
1079         for (Map.Entry<String, Integer> entry : createPublicPredefinedEffectsMap().entrySet()) {
1080             VibrationEffect preamble = VibrationEffect.get(entry.getValue());
1081             VibrationEffect repeating = VibrationEffect.get(entry.getValue());
1082             VibrationEffect effect = VibrationEffect.createRepeatingEffect(preamble, repeating);
1083             String xml = String.format("""
1084                     <vibration-effect>
1085                         <repeating-effect>
1086                             <preamble>
1087                                 <predefined-effect name="%s"/>
1088                             </preamble>
1089                             <repeating>
1090                                 <predefined-effect name="%s"/>
1091                             </repeating>
1092                         </repeating-effect>
1093                     </vibration-effect>
1094                     """,
1095                     entry.getKey(), entry.getKey());
1096 
1097             assertPublicApisParserSucceeds(xml, effect);
1098             assertPublicApisSerializerSucceeds(effect, entry.getKey());
1099             assertPublicApisRoundTrip(effect);
1100 
1101             assertHiddenApisParserSucceeds(xml, effect);
1102             assertHiddenApisSerializerSucceeds(effect, entry.getKey());
1103             assertHiddenApisRoundTrip(effect);
1104 
1105             effect = VibrationEffect.createRepeatingEffect(repeating);
1106             xml = String.format("""
1107                     <vibration-effect>
1108                         <repeating-effect>
1109                             <repeating>
1110                                 <predefined-effect name="%s"/>
1111                             </repeating>
1112                         </repeating-effect>
1113                     </vibration-effect>
1114                     """,
1115                     entry.getKey());
1116 
1117             assertPublicApisParserSucceeds(xml, effect);
1118             assertPublicApisSerializerSucceeds(effect, entry.getKey());
1119             assertPublicApisRoundTrip(effect);
1120 
1121             assertHiddenApisParserSucceeds(xml, effect);
1122             assertHiddenApisSerializerSucceeds(effect, entry.getKey());
1123             assertHiddenApisRoundTrip(effect);
1124         }
1125     }
1126 
1127     @Test
1128     @EnableFlags(Flags.FLAG_NORMALIZED_PWLE_EFFECTS)
testRepeating_withWaveformEntry_allSucceed()1129     public void testRepeating_withWaveformEntry_allSucceed() throws Exception {
1130         VibrationEffect preamble = VibrationEffect.createWaveform(new long[]{123, 456, 789, 0},
1131                 new int[]{254, 1, 255, 0}, /* repeat= */ -1);
1132         VibrationEffect repeating = VibrationEffect.createWaveform(new long[]{123, 456, 789, 0},
1133                 new int[]{254, 1, 255, 0}, /* repeat= */ -1);
1134         VibrationEffect effect = VibrationEffect.createRepeatingEffect(preamble, repeating);
1135 
1136         String xml = """
1137                 <vibration-effect>
1138                     <repeating-effect>
1139                         <preamble>
1140                             <waveform-entry durationMs="123" amplitude="254"/>
1141                             <waveform-entry durationMs="456" amplitude="1"/>
1142                             <waveform-entry durationMs="789" amplitude="255"/>
1143                             <waveform-entry durationMs="0" amplitude="0"/>
1144                         </preamble>
1145                         <repeating>
1146                             <waveform-entry durationMs="123" amplitude="254"/>
1147                             <waveform-entry durationMs="456" amplitude="1"/>
1148                             <waveform-entry durationMs="789" amplitude="255"/>
1149                             <waveform-entry durationMs="0" amplitude="0"/>
1150                         </repeating>
1151                     </repeating-effect>
1152                 </vibration-effect>
1153                 """;
1154 
1155         assertPublicApisParserSucceeds(xml, effect);
1156         assertPublicApisSerializerSucceeds(effect, "123", "456", "789", "254", "1", "255", "0");
1157         assertPublicApisRoundTrip(effect);
1158 
1159         assertHiddenApisParserSucceeds(xml, effect);
1160         assertHiddenApisSerializerSucceeds(effect, "123", "456", "789", "254", "1", "255", "0");
1161         assertHiddenApisRoundTrip(effect);
1162 
1163         xml = """
1164                 <vibration-effect>
1165                     <repeating-effect>
1166                         <repeating>
1167                             <waveform-entry durationMs="123" amplitude="254"/>
1168                             <waveform-entry durationMs="456" amplitude="1"/>
1169                             <waveform-entry durationMs="789" amplitude="255"/>
1170                             <waveform-entry durationMs="0" amplitude="0"/>
1171                         </repeating>
1172                     </repeating-effect>
1173                 </vibration-effect>
1174                 """;
1175 
1176         effect = VibrationEffect.createRepeatingEffect(repeating);
1177 
1178         assertPublicApisParserSucceeds(xml, effect);
1179         assertPublicApisSerializerSucceeds(effect, "123", "456", "789", "254", "1", "255", "0");
1180         assertPublicApisRoundTrip(effect);
1181 
1182         assertHiddenApisParserSucceeds(xml, effect);
1183         assertHiddenApisSerializerSucceeds(effect, "123", "456", "789", "254", "1", "255", "0");
1184         assertHiddenApisRoundTrip(effect);
1185     }
1186 
1187     @Test
1188     @EnableFlags(Flags.FLAG_NORMALIZED_PWLE_EFFECTS)
testRepeating_withPrimitives_allSucceed()1189     public void testRepeating_withPrimitives_allSucceed() throws Exception {
1190         VibrationEffect preamble = VibrationEffect.startComposition()
1191                 .addPrimitive(PRIMITIVE_CLICK)
1192                 .addPrimitive(PRIMITIVE_TICK, 0.2497f)
1193                 .addPrimitive(PRIMITIVE_LOW_TICK, 1f, 356)
1194                 .addPrimitive(PRIMITIVE_SPIN, 0.6364f, 7)
1195                 .compose();
1196         VibrationEffect repeating = VibrationEffect.startComposition()
1197                 .addPrimitive(PRIMITIVE_CLICK)
1198                 .addPrimitive(PRIMITIVE_TICK, 0.2497f)
1199                 .addPrimitive(PRIMITIVE_LOW_TICK, 1f, 356)
1200                 .addPrimitive(PRIMITIVE_SPIN, 0.6364f, 7)
1201                 .compose();
1202         VibrationEffect effect = VibrationEffect.createRepeatingEffect(preamble, repeating);
1203 
1204         String xml = """
1205                 <vibration-effect>
1206                     <repeating-effect>
1207                         <preamble>
1208                             <primitive-effect name="click" />
1209                             <primitive-effect name="tick" scale="0.2497" />
1210                             <primitive-effect name="low_tick" delayMs="356" />
1211                             <primitive-effect name="spin" scale="0.6364" delayMs="7" />
1212                         </preamble>
1213                         <repeating>
1214                             <primitive-effect name="click" />
1215                             <primitive-effect name="tick" scale="0.2497" />
1216                             <primitive-effect name="low_tick" delayMs="356" />
1217                             <primitive-effect name="spin" scale="0.6364" delayMs="7" />
1218                         </repeating>
1219                     </repeating-effect>
1220                 </vibration-effect>
1221                 """;
1222 
1223         assertPublicApisParserSucceeds(xml, effect);
1224         assertPublicApisSerializerSucceeds(effect, "click", "tick", "low_tick", "spin");
1225         assertPublicApisRoundTrip(effect);
1226 
1227         assertHiddenApisParserSucceeds(xml, effect);
1228         assertHiddenApisSerializerSucceeds(effect, "click", "tick", "low_tick", "spin");
1229         assertHiddenApisRoundTrip(effect);
1230 
1231         repeating = VibrationEffect.startComposition()
1232                 .addPrimitive(PRIMITIVE_CLICK)
1233                 .addPrimitive(PRIMITIVE_TICK, 0.2497f)
1234                 .addPrimitive(PRIMITIVE_LOW_TICK, 1f, 356)
1235                 .addPrimitive(PRIMITIVE_SPIN, 0.6364f, 7)
1236                 .compose();
1237         effect = VibrationEffect.createRepeatingEffect(repeating);
1238 
1239         xml = """
1240                 <vibration-effect>
1241                     <repeating-effect>
1242                         <repeating>
1243                             <primitive-effect name="click" />
1244                             <primitive-effect name="tick" scale="0.2497" />
1245                             <primitive-effect name="low_tick" delayMs="356" />
1246                             <primitive-effect name="spin" scale="0.6364" delayMs="7" />
1247                         </repeating>
1248                     </repeating-effect>
1249                 </vibration-effect>
1250                 """;
1251 
1252         assertPublicApisParserSucceeds(xml, effect);
1253         assertPublicApisSerializerSucceeds(effect, "click", "tick", "low_tick", "spin");
1254         assertPublicApisRoundTrip(effect);
1255 
1256         assertHiddenApisParserSucceeds(xml, effect);
1257         assertHiddenApisSerializerSucceeds(effect, "click", "tick", "low_tick", "spin");
1258         assertHiddenApisRoundTrip(effect);
1259     }
1260 
1261     @Test
1262     @EnableFlags(Flags.FLAG_NORMALIZED_PWLE_EFFECTS)
testRepeating_withMixedVibrations_allSucceed()1263     public void testRepeating_withMixedVibrations_allSucceed() throws Exception {
1264         VibrationEffect preamble = new VibrationEffect.WaveformEnvelopeBuilder()
1265                 .addControlPoint(0.1f, 50f, 10)
1266                 .build();
1267         VibrationEffect repeating = VibrationEffect.get(VibrationEffect.EFFECT_TICK);
1268         VibrationEffect effect = VibrationEffect.createRepeatingEffect(preamble, repeating);
1269         String xml = """
1270                     <vibration-effect>
1271                         <repeating-effect>
1272                             <preamble>
1273                                 <waveform-envelope-effect>
1274                                 <control-point amplitude="0.1" frequencyHz="50.0" durationMs="10"/>
1275                                 </waveform-envelope-effect>
1276                             </preamble>
1277                             <repeating>
1278                                 <predefined-effect name="tick"/>
1279                             </repeating>
1280                         </repeating-effect>
1281                     </vibration-effect>
1282                     """;
1283         assertPublicApisParserSucceeds(xml, effect);
1284         assertPublicApisSerializerSucceeds(effect, "0.1", "50.0", "10", "tick");
1285         assertPublicApisRoundTrip(effect);
1286 
1287         assertHiddenApisParserSucceeds(xml, effect);
1288         assertHiddenApisSerializerSucceeds(effect, "0.1", "50.0", "10", "tick");
1289         assertHiddenApisRoundTrip(effect);
1290 
1291         preamble = VibrationEffect.createWaveform(new long[]{123, 456},
1292                 new int[]{254, 1}, /* repeat= */ -1);
1293         repeating = new VibrationEffect.BasicEnvelopeBuilder()
1294                 .addControlPoint(0.3f, 0.4f, 25)
1295                 .addControlPoint(0.0f, 0.5f, 30)
1296                 .build();
1297         effect = VibrationEffect.createRepeatingEffect(preamble, repeating);
1298 
1299         xml = """
1300                 <vibration-effect>
1301                     <repeating-effect>
1302                         <preamble>
1303                             <waveform-entry durationMs="123" amplitude="254"/>
1304                             <waveform-entry durationMs="456" amplitude="1"/>
1305                         </preamble>
1306                         <repeating>
1307                         <basic-envelope-effect>
1308                             <control-point intensity="0.3" sharpness="0.4" durationMs="25" />
1309                             <control-point intensity="0.0" sharpness="0.5" durationMs="30" />
1310                         </basic-envelope-effect>
1311                         </repeating>
1312                     </repeating-effect>
1313                 </vibration-effect>
1314                 """;
1315 
1316         assertPublicApisParserSucceeds(xml, effect);
1317         assertPublicApisSerializerSucceeds(effect, "123", "456", "254", "1", "0.3", "0.0", "0.4",
1318                 "0.5", "25", "30");
1319         assertPublicApisRoundTrip(effect);
1320 
1321         assertHiddenApisParserSucceeds(xml, effect);
1322         assertHiddenApisSerializerSucceeds(effect, "123", "456", "254", "1", "0.3", "0.0", "0.4",
1323                 "0.5", "25", "30");
1324         assertHiddenApisRoundTrip(effect);
1325 
1326         preamble = VibrationEffect.startComposition()
1327                 .addPrimitive(PRIMITIVE_CLICK)
1328                 .compose();
1329         effect = VibrationEffect.createRepeatingEffect(preamble, repeating);
1330 
1331         xml = """
1332                 <vibration-effect>
1333                     <repeating-effect>
1334                         <preamble>
1335                             <primitive-effect name="click" />
1336                         </preamble>
1337                         <repeating>
1338                             <basic-envelope-effect>
1339                                 <control-point intensity="0.3" sharpness="0.4" durationMs="25" />
1340                                 <control-point intensity="0.0" sharpness="0.5" durationMs="30" />
1341                             </basic-envelope-effect>
1342                         </repeating>
1343                     </repeating-effect>
1344                 </vibration-effect>
1345                 """;
1346 
1347         assertPublicApisParserSucceeds(xml, effect);
1348         assertPublicApisSerializerSucceeds(effect, "click", "0.3", "0.4", "0.0", "0.5", "25", "30");
1349         assertPublicApisRoundTrip(effect);
1350 
1351         assertHiddenApisParserSucceeds(xml, effect);
1352         assertHiddenApisSerializerSucceeds(effect, "click", "0.3", "0.4", "0.0", "0.5", "25", "30");
1353         assertHiddenApisRoundTrip(effect);
1354     }
1355 
1356     @Test
1357     @EnableFlags(Flags.FLAG_NORMALIZED_PWLE_EFFECTS)
testRepeating_badXml_throwsException()1358     public void testRepeating_badXml_throwsException() throws IOException {
1359         // Incomplete XML
1360         assertParseElementFails("""
1361                 <vibration-effect>
1362                 <repeating-effect>
1363                     <preamble>
1364                         <primitive-effect name="click" />
1365                     </preamble>
1366                     <repeating>
1367                         <primitive-effect name="click" />
1368                 """);
1369 
1370         assertParseElementFails("""
1371                 <vibration-effect>
1372                 <repeating-effect>
1373                         <primitive-effect name="click" />
1374                     <repeating>
1375                         <primitive-effect name="click" />
1376                     </repeating>
1377                 </repeating-effect>
1378                 </vibration-effect>
1379                 """);
1380 
1381         assertParseElementFails("""
1382                 <vibration-effect>
1383                 <repeating-effect>
1384                     <preamble>
1385                         <primitive-effect name="click" />
1386                     </preamble>
1387                         <primitive-effect name="click" />
1388                 </repeating-effect>
1389                 </vibration-effect>
1390                 """);
1391 
1392         // Bad vibration XML
1393         assertParseElementFails("""
1394                 <vibration-effect>
1395                 <repeating-effect>
1396                     <repeating>
1397                         <primitive-effect name="click" />
1398                     </repeating>
1399                     <preamble>
1400                         <primitive-effect name="click" />
1401                     </preamble>
1402                 </repeating-effect>
1403                 </vibration-effect>
1404                 """);
1405 
1406         assertParseElementFails("""
1407                 <vibration-effect>
1408                 <repeating-effect>
1409                     <repeating>
1410                     <preamble>
1411                         <primitive-effect name="click" />
1412                     </preamble>
1413                         <primitive-effect name="click" />
1414                     </repeating>
1415                 </repeating-effect>
1416                 </vibration-effect>
1417                 """);
1418 
1419         assertParseElementFails("""
1420                 <vibration-effect>
1421                 <repeating-effect>
1422                     <preamble>
1423                         <primitive-effect name="click" />
1424                     <repeating>
1425                         <primitive-effect name="click" />
1426                     </repeating>
1427                     </preamble>
1428                 </repeating-effect>
1429                 </vibration-effect>
1430                 """);
1431 
1432         assertParseElementFails("""
1433                 <vibration-effect>
1434                 <repeating-effect>
1435                     <primitive-effect name="click" />
1436                     <primitive-effect name="click" />
1437                 </repeating-effect>
1438                 </vibration-effect>
1439                 """);
1440     }
1441 
1442     @Test
1443     @DisableFlags(Flags.FLAG_NORMALIZED_PWLE_EFFECTS)
testRepeatingEffect_featureFlagDisabled_allFail()1444     public void testRepeatingEffect_featureFlagDisabled_allFail() throws Exception {
1445         VibrationEffect repeating = VibrationEffect.startComposition()
1446                 .addPrimitive(PRIMITIVE_CLICK)
1447                 .addPrimitive(PRIMITIVE_TICK, 0.2497f)
1448                 .addPrimitive(PRIMITIVE_LOW_TICK, 1f, 356)
1449                 .addPrimitive(PRIMITIVE_SPIN, 0.6364f, 7)
1450                 .compose();
1451         VibrationEffect effect = VibrationEffect.createRepeatingEffect(repeating);
1452 
1453         String xml = """
1454                 <vibration-effect>
1455                     <repeating-effect>
1456                         <repeating>
1457                             <primitive-effect name="click" />
1458                             <primitive-effect name="tick" scale="0.2497" />
1459                             <primitive-effect name="low_tick" delayMs="356" />
1460                             <primitive-effect name="spin" scale="0.6364" delayMs="7" />
1461                         </repeating>
1462                     </repeating-effect>
1463                 </vibration-effect>
1464                 """;
1465 
1466         assertPublicApisParserFails(xml);
1467         assertPublicApisSerializerFails(effect);
1468         assertHiddenApisParserFails(xml);
1469         assertHiddenApisSerializerFails(effect);
1470     }
1471 
1472     @Test
1473     @EnableFlags(Flags.FLAG_VENDOR_VIBRATION_EFFECTS)
testVendorEffect_allSucceed()1474     public void testVendorEffect_allSucceed() throws Exception {
1475         PersistableBundle vendorData = new PersistableBundle();
1476         vendorData.putInt("id", 1);
1477         vendorData.putDouble("scale", 0.5);
1478         vendorData.putBoolean("loop", false);
1479         vendorData.putLongArray("amplitudes", new long[] { 0, 255, 128 });
1480         vendorData.putString("label", "vibration");
1481 
1482         ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
1483         vendorData.writeToStream(outputStream);
1484         String vendorDataStr = Base64.getEncoder().encodeToString(outputStream.toByteArray());
1485 
1486         VibrationEffect effect = VibrationEffect.createVendorEffect(vendorData);
1487         String xml = "<vibration-effect><vendor-effect>  " // test trailing whitespace is ignored
1488                 + vendorDataStr
1489                 + " \n </vendor-effect></vibration-effect>";
1490 
1491         assertPublicApisParserSucceeds(xml, effect);
1492         assertPublicApisSerializerSucceeds(effect, vendorDataStr);
1493         assertPublicApisRoundTrip(effect);
1494 
1495         assertHiddenApisParserSucceeds(xml, effect);
1496         assertHiddenApisSerializerSucceeds(effect, vendorDataStr);
1497         assertHiddenApisRoundTrip(effect);
1498 
1499         // Check PersistableBundle from round-trip
1500         PersistableBundle parsedVendorData =
1501                 ((VibrationEffect.VendorEffect) parseVibrationEffect(serialize(effect),
1502                         /* flags= */ 0)).getVendorData();
1503         assertThat(parsedVendorData.size()).isEqualTo(vendorData.size());
1504         assertThat(parsedVendorData.getInt("id")).isEqualTo(1);
1505         assertThat(parsedVendorData.getDouble("scale")).isEqualTo(0.5);
1506         assertThat(parsedVendorData.getBoolean("loop")).isFalse();
1507         assertArrayEquals(parsedVendorData.getLongArray("amplitudes"), new long[] { 0, 255, 128 });
1508         assertThat(parsedVendorData.getString("label")).isEqualTo("vibration");
1509     }
1510 
1511     @Test
1512     @EnableFlags(Flags.FLAG_VENDOR_VIBRATION_EFFECTS)
testInvalidVendorEffect_allFail()1513     public void testInvalidVendorEffect_allFail() throws IOException {
1514         String emptyTag = "<vibration-effect><vendor-effect/></vibration-effect>";
1515         assertPublicApisParserFails(emptyTag);
1516         assertHiddenApisParserFails(emptyTag);
1517 
1518         String emptyStringTag =
1519                 "<vibration-effect><vendor-effect> \n </vendor-effect></vibration-effect>";
1520         assertPublicApisParserFails(emptyStringTag);
1521         assertHiddenApisParserFails(emptyStringTag);
1522 
1523         String invalidString =
1524                 "<vibration-effect><vendor-effect>invalid</vendor-effect></vibration-effect>";
1525         assertPublicApisParserFails(invalidString);
1526         assertHiddenApisParserFails(invalidString);
1527 
1528         String validBase64String =
1529                 "<vibration-effect><vendor-effect>c29tZXNh</vendor-effect></vibration-effect>";
1530         assertPublicApisParserFails(validBase64String);
1531         assertHiddenApisParserFails(validBase64String);
1532 
1533         PersistableBundle emptyData = new PersistableBundle();
1534         ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
1535         emptyData.writeToStream(outputStream);
1536         String emptyBundleString = "<vibration-effect><vendor-effect>"
1537                 + Base64.getEncoder().encodeToString(outputStream.toByteArray())
1538                 + "</vendor-effect></vibration-effect>";
1539         assertPublicApisParserFails(emptyBundleString);
1540         assertHiddenApisParserFails(emptyBundleString);
1541     }
1542 
1543     @Test
1544     @DisableFlags(Flags.FLAG_VENDOR_VIBRATION_EFFECTS)
testVendorEffect_featureFlagDisabled_allFail()1545     public void testVendorEffect_featureFlagDisabled_allFail() throws Exception {
1546         PersistableBundle vendorData = new PersistableBundle();
1547         vendorData.putInt("id", 1);
1548         ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
1549         vendorData.writeToStream(outputStream);
1550         String vendorDataStr = Base64.getEncoder().encodeToString(outputStream.toByteArray());
1551         String xml = "<vibration-effect><vendor-effect>"
1552                 + vendorDataStr
1553                 + "</vendor-effect></vibration-effect>";
1554         VibrationEffect vendorEffect = VibrationEffect.createVendorEffect(vendorData);
1555 
1556         assertPublicApisParserFails(xml);
1557         assertPublicApisSerializerFails(vendorEffect);
1558 
1559         assertHiddenApisParserFails(xml);
1560         assertHiddenApisSerializerFails(vendorEffect);
1561     }
1562 
1563     @Test
1564     @EnableFlags(Flags.FLAG_PRIMITIVE_COMPOSITION_ABSOLUTE_DELAY)
testPrimitiveDelayType_allSucceed()1565     public void testPrimitiveDelayType_allSucceed() throws Exception {
1566         VibrationEffect effect = VibrationEffect.startComposition()
1567                 .addPrimitive(PRIMITIVE_TICK, 1.0f, 0, DELAY_TYPE_RELATIVE_START_OFFSET)
1568                 .addPrimitive(PRIMITIVE_CLICK, 0.123f, 10, DELAY_TYPE_PAUSE)
1569                 .compose();
1570         String xml = """
1571                 <vibration-effect>
1572                     <primitive-effect name="tick" delayType="relative_start_offset"/>
1573                     <primitive-effect name="click" scale="0.123" delayMs="10"/>
1574                 </vibration-effect>
1575                 """;
1576 
1577         assertPublicApisParserSucceeds(xml, effect);
1578         assertPublicApisSerializerSucceeds(effect, "tick", "click");
1579         // Delay type pause is not serialized, as it's the default one
1580         assertPublicApisSerializerSucceeds(effect, "relative_start_offset", "click");
1581         assertPublicApisRoundTrip(effect);
1582 
1583         assertHiddenApisParserSucceeds(xml, effect);
1584         assertHiddenApisSerializerSucceeds(effect, "tick", "click");
1585         assertHiddenApisRoundTrip(effect);
1586 
1587         // Check PersistableBundle from round-trip
1588         VibrationEffect.Composed parsedEffect = ((VibrationEffect.Composed) parseVibrationEffect(
1589                 serialize(effect), /* flags= */ 0));
1590         assertThat(parsedEffect.getRepeatIndex()).isEqualTo(-1);
1591         assertThat(parsedEffect.getSegments()).containsExactly(
1592                 new PrimitiveSegment(PRIMITIVE_TICK, 1.0f, 0, DELAY_TYPE_RELATIVE_START_OFFSET),
1593                 new PrimitiveSegment(PRIMITIVE_CLICK, 0.123f, 10, DELAY_TYPE_PAUSE))
1594                 .inOrder();
1595     }
1596 
1597     @Test
1598     @EnableFlags(Flags.FLAG_PRIMITIVE_COMPOSITION_ABSOLUTE_DELAY)
testPrimitiveInvalidDelayType_allFail()1599     public void testPrimitiveInvalidDelayType_allFail() {
1600         String emptyAttribute = """
1601                 <vibration-effect>
1602                     <primitive-effect name="tick" delayType=""/>
1603                 </vibration-effect>
1604                 """;
1605         assertPublicApisParserFails(emptyAttribute);
1606         assertHiddenApisParserFails(emptyAttribute);
1607 
1608         String invalidString = """
1609                 <vibration-effect>
1610                     <primitive-effect name="tick" delayType="invalid"/>
1611                 </vibration-effect>
1612                 """;
1613         assertPublicApisParserFails(invalidString);
1614         assertHiddenApisParserFails(invalidString);
1615     }
1616 
1617     @Test
1618     @DisableFlags(Flags.FLAG_PRIMITIVE_COMPOSITION_ABSOLUTE_DELAY)
testPrimitiveDelayType_featureFlagDisabled_allFail()1619     public void testPrimitiveDelayType_featureFlagDisabled_allFail() {
1620         VibrationEffect effect = VibrationEffect.startComposition()
1621                 .addPrimitive(PRIMITIVE_TICK, 1.0f, 0, DELAY_TYPE_RELATIVE_START_OFFSET)
1622                 .addPrimitive(PRIMITIVE_CLICK, 0.123f, 10, DELAY_TYPE_PAUSE)
1623                 .compose();
1624         String xml = """
1625                 <vibration-effect>
1626                     <primitive-effect name="tick" delayType="relative_start_offset"/>
1627                     <primitive-effect name="click" scale="0.123" delayMs="10" delayType="pause"/>
1628                 </vibration-effect>
1629                 """;
1630 
1631         assertPublicApisParserFails(xml);
1632         assertPublicApisSerializerFails(effect);
1633 
1634         assertHiddenApisParserFails(xml);
1635         assertHiddenApisSerializerFails(effect);
1636     }
1637 
assertPublicApisParserFails(String xml)1638     private void assertPublicApisParserFails(String xml) {
1639         assertThrows("Expected parseVibrationEffect to fail for " + xml,
1640                 VibrationXmlParser.ParseFailedException.class,
1641                 () -> parseVibrationEffect(xml, /* flags= */ 0));
1642         assertThrows("Expected parseDocument to fail for " + xml,
1643                 VibrationXmlParser.ParseFailedException.class,
1644                 () -> parseDocument(xml, /* flags= */ 0));
1645     }
1646 
assertPublicApisParseVibrationEffectFails(String xml)1647     private void assertPublicApisParseVibrationEffectFails(String xml) {
1648         assertThrows("Expected parseVibrationEffect to fail for " + xml,
1649                 VibrationXmlParser.ParseFailedException.class,
1650                 () -> parseVibrationEffect(xml, /* flags= */ 0));
1651     }
1652 
assertPublicApisParserSucceeds(String xml, VibrationEffect effect)1653     private void assertPublicApisParserSucceeds(String xml, VibrationEffect effect)
1654             throws Exception {
1655         assertPublicApisParseDocumentSucceeds(xml, effect);
1656         assertPublicApisParseVibrationEffectSucceeds(xml, effect);
1657     }
1658 
assertPublicApisParseDocumentSucceeds(String xml, VibrationEffect... effects)1659     private void assertPublicApisParseDocumentSucceeds(String xml, VibrationEffect... effects)
1660             throws Exception {
1661         assertThat(parseDocument(xml, /* flags= */ 0))
1662                 .isEqualTo(new ParsedVibration(Arrays.asList(effects)));
1663     }
1664 
assertPublicApisParseVibrationEffectSucceeds(String xml, VibrationEffect effect)1665     private void assertPublicApisParseVibrationEffectSucceeds(String xml, VibrationEffect effect)
1666             throws Exception {
1667         assertThat(parseVibrationEffect(xml, /* flags= */ 0)).isEqualTo(effect);
1668     }
1669 
assertHiddenApisParserFails(String xml)1670     private void assertHiddenApisParserFails(String xml) {
1671         assertThrows("Expected parseVibrationEffect to fail for " + xml,
1672                 VibrationXmlParser.ParseFailedException.class,
1673                 () -> parseVibrationEffect(xml, VibrationXmlParser.FLAG_ALLOW_HIDDEN_APIS));
1674         assertThrows("Expected parseDocument to fail for " + xml,
1675                 VibrationXmlParser.ParseFailedException.class,
1676                 () -> parseDocument(xml, VibrationXmlParser.FLAG_ALLOW_HIDDEN_APIS));
1677     }
1678 
assertHiddenApisParseVibrationEffectFails(String xml)1679     private void assertHiddenApisParseVibrationEffectFails(String xml) {
1680         assertThrows("Expected parseVibrationEffect to fail for " + xml,
1681                 VibrationXmlParser.ParseFailedException.class,
1682                 () -> parseVibrationEffect(xml, VibrationXmlParser.FLAG_ALLOW_HIDDEN_APIS));
1683     }
1684 
assertHiddenApisParserSucceeds(String xml, VibrationEffect effect)1685     private void assertHiddenApisParserSucceeds(String xml, VibrationEffect effect)
1686             throws Exception {
1687         assertHiddenApisParseDocumentSucceeds(xml, effect);
1688         assertHiddenApisParseVibrationEffectSucceeds(xml, effect);
1689     }
1690 
assertHiddenApisParseDocumentSucceeds(String xml, VibrationEffect... effect)1691     private void assertHiddenApisParseDocumentSucceeds(String xml, VibrationEffect... effect)
1692             throws Exception {
1693         assertThat(parseDocument(xml, VibrationXmlParser.FLAG_ALLOW_HIDDEN_APIS))
1694                 .isEqualTo(new ParsedVibration(Arrays.asList(effect)));
1695     }
1696 
assertHiddenApisParseVibrationEffectSucceeds(String xml, VibrationEffect effect)1697     private void assertHiddenApisParseVibrationEffectSucceeds(String xml, VibrationEffect effect)
1698             throws Exception {
1699         assertThat(parseVibrationEffect(xml, VibrationXmlParser.FLAG_ALLOW_HIDDEN_APIS))
1700                 .isEqualTo(effect);
1701     }
1702 
assertPublicApisSerializerFails(VibrationEffect effect)1703     private void assertPublicApisSerializerFails(VibrationEffect effect) {
1704         assertThrows("Expected serialization to fail for " + effect,
1705                 VibrationXmlSerializer.SerializationFailedException.class,
1706                 () -> serialize(effect));
1707     }
1708 
assertHiddenApisSerializerFails(VibrationEffect effect)1709     private void assertHiddenApisSerializerFails(VibrationEffect effect) {
1710         assertThrows("Expected serialization to fail for " + effect,
1711                 VibrationXmlSerializer.SerializationFailedException.class,
1712                 () -> serialize(effect, VibrationXmlSerializer.FLAG_ALLOW_HIDDEN_APIS));
1713     }
1714 
assertPublicApisSerializerSucceeds(VibrationEffect effect, String... expectedSegments)1715     private void assertPublicApisSerializerSucceeds(VibrationEffect effect,
1716             String... expectedSegments) throws Exception {
1717         assertSerializationContainsSegments(serialize(effect), expectedSegments);
1718     }
1719 
assertHiddenApisSerializerSucceeds(VibrationEffect effect, String... expectedSegments)1720     private void assertHiddenApisSerializerSucceeds(VibrationEffect effect,
1721             String... expectedSegments) throws Exception {
1722         assertSerializationContainsSegments(
1723                 serialize(effect, VibrationXmlSerializer.FLAG_ALLOW_HIDDEN_APIS), expectedSegments);
1724     }
1725 
assertPublicApisRoundTrip(VibrationEffect effect)1726     private void assertPublicApisRoundTrip(VibrationEffect effect) throws Exception {
1727         assertThat(parseVibrationEffect(serialize(effect, /* flags= */ 0), /* flags= */ 0))
1728                 .isEqualTo(effect);
1729     }
1730 
assertHiddenApisRoundTrip(VibrationEffect effect)1731     private void assertHiddenApisRoundTrip(VibrationEffect effect) throws Exception {
1732         String xml = serialize(effect, VibrationXmlSerializer.FLAG_ALLOW_HIDDEN_APIS);
1733         assertThat(parseVibrationEffect(xml, VibrationXmlParser.FLAG_ALLOW_HIDDEN_APIS))
1734                 .isEqualTo(effect);
1735     }
1736 
createXmlPullParser(String xml)1737     private TypedXmlPullParser createXmlPullParser(String xml) throws Exception {
1738         TypedXmlPullParser parser = Xml.newFastPullParser();
1739         parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true);
1740         parser.setInput(new StringReader(xml));
1741         parser.next(); // read START_DOCUMENT
1742         return parser;
1743     }
1744 
1745     /**
1746      * Asserts parsing vibration from an open TypedXmlPullParser succeeds, and that the parser
1747      * points to the end "vibration" or "vibration-select" tag.
1748      */
assertParseElementSucceeds( TypedXmlPullParser parser, VibrationEffect... effects)1749     private void assertParseElementSucceeds(
1750             TypedXmlPullParser parser, VibrationEffect... effects) throws Exception {
1751         assertParseElementSucceeds(parser, VibrationXmlParser.FLAG_ALLOW_HIDDEN_APIS, effects);
1752     }
1753 
assertParseElementSucceeds( TypedXmlPullParser parser, int flags, VibrationEffect... effects)1754     private void assertParseElementSucceeds(
1755             TypedXmlPullParser parser, int flags, VibrationEffect... effects) throws Exception {
1756         String tagName = parser.getName();
1757         assertThat(Set.of("vibration-effect", "vibration-select")).contains(tagName);
1758 
1759         assertThat(parseElement(parser, flags))
1760                 .isEqualTo(new ParsedVibration(Arrays.asList(effects)));
1761         assertThat(parser.getEventType()).isEqualTo(XmlPullParser.END_TAG);
1762         assertThat(parser.getName()).isEqualTo(tagName);
1763     }
1764 
assertEndTag(TypedXmlPullParser parser, String expectedTagName)1765     private void assertEndTag(TypedXmlPullParser parser, String expectedTagName) throws Exception {
1766         assertThat(parser.getName()).isEqualTo(expectedTagName);
1767         assertThat(parser.getEventType()).isEqualTo(parser.END_TAG);
1768     }
1769 
assertStartTag(TypedXmlPullParser parser, String expectedTagName)1770     private void assertStartTag(TypedXmlPullParser parser, String expectedTagName)
1771             throws Exception {
1772         assertThat(parser.getName()).isEqualTo(expectedTagName);
1773         assertThat(parser.getEventType()).isEqualTo(parser.START_TAG);
1774     }
1775 
assertEndOfDocument(TypedXmlPullParser parser)1776     private void assertEndOfDocument(TypedXmlPullParser parser) throws Exception {
1777         assertThat(parser.getEventType()).isEqualTo(parser.END_DOCUMENT);
1778     }
1779 
assertParseElementFails(String xml)1780     private void assertParseElementFails(String xml) {
1781         assertThrows("Expected parsing to fail for " + xml,
1782                 VibrationXmlParser.ParseFailedException.class,
1783                 () -> parseElement(createXmlPullParser(xml), /* flags= */ 0));
1784     }
1785 
assertSerializationContainsSegments(String xml, String[] expectedSegments)1786     private void assertSerializationContainsSegments(String xml, String[] expectedSegments) {
1787         for (String expectedSegment : expectedSegments) {
1788             assertThat(xml).contains(expectedSegment);
1789         }
1790     }
1791 
parseVibrationEffect( String xml, @VibrationXmlParser.Flags int flags)1792     private static VibrationEffect parseVibrationEffect(
1793             String xml, @VibrationXmlParser.Flags int flags) throws Exception {
1794         return VibrationXmlParser.parseVibrationEffect(new StringReader(xml), flags);
1795     }
1796 
parseDocument(String xml, int flags)1797     private static ParsedVibration parseDocument(String xml, int flags) throws Exception {
1798         return VibrationXmlParser.parseDocument(new StringReader(xml), flags);
1799     }
1800 
parseElement(TypedXmlPullParser parser, int flags)1801     private static ParsedVibration parseElement(TypedXmlPullParser parser, int flags)
1802             throws Exception {
1803         return VibrationXmlParser.parseElement(parser, flags);
1804     }
1805 
serialize(VibrationEffect effect)1806     private static String serialize(VibrationEffect effect) throws Exception {
1807         StringWriter writer = new StringWriter();
1808         VibrationXmlSerializer.serialize(effect, writer);
1809         return writer.toString();
1810     }
1811 
serialize(VibrationEffect effect, @VibrationXmlSerializer.Flags int flags)1812     private static String serialize(VibrationEffect effect, @VibrationXmlSerializer.Flags int flags)
1813             throws Exception {
1814         StringWriter writer = new StringWriter();
1815         VibrationXmlSerializer.serialize(effect, writer, flags);
1816         return writer.toString();
1817     }
1818 
createAllPredefinedEffectsMap()1819     private static Map<String, Integer> createAllPredefinedEffectsMap() {
1820         Map<String, Integer> map = createHiddenPredefinedEffectsMap();
1821         map.putAll(createPublicPredefinedEffectsMap());
1822         return map;
1823     }
1824 
createPublicPredefinedEffectsMap()1825     private static Map<String, Integer> createPublicPredefinedEffectsMap() {
1826         Map<String, Integer> map = new HashMap<>();
1827         map.put("tick", VibrationEffect.EFFECT_TICK);
1828         map.put("click", VibrationEffect.EFFECT_CLICK);
1829         map.put("heavy_click", VibrationEffect.EFFECT_HEAVY_CLICK);
1830         map.put("double_click", VibrationEffect.EFFECT_DOUBLE_CLICK);
1831         return map;
1832     }
1833 
createHiddenPredefinedEffectsMap()1834     private static Map<String, Integer> createHiddenPredefinedEffectsMap() {
1835         Map<String, Integer> map = new HashMap<>();
1836         map.put("texture_tick", VibrationEffect.EFFECT_TEXTURE_TICK);
1837         map.put("pop", VibrationEffect.EFFECT_POP);
1838         map.put("thud", VibrationEffect.EFFECT_THUD);
1839         for (int i = 0; i < VibrationEffect.RINGTONES.length; i++) {
1840             map.put(String.format("ringtone_%d", i + 1), VibrationEffect.RINGTONES[i]);
1841         }
1842         return map;
1843     }
1844 }
1845