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