summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Yeabkal Wubshit <yeabkal@google.com> 2023-08-24 21:16:06 +0000
committer Android (Google) Code Review <android-gerrit@google.com> 2023-08-24 21:16:06 +0000
commitdca03676602c194e31227c1b37696e90d2294b4b (patch)
tree4aa25d0072f75b9224844e06d578dbcc872e57b4
parent653581a0499ba49a6738cbb4778cc80c02c3302d (diff)
parentc908e136318b67538c4c1c9a1f7e2cca67b5d9e0 (diff)
Merge "Support vibration-select parsing" into main
-rw-r--r--core/api/test-current.txt8
-rw-r--r--core/java/android/hardware/input/InputDeviceVibrator.java2
-rw-r--r--core/java/android/os/SystemVibrator.java2
-rw-r--r--core/java/android/os/SystemVibratorManager.java2
-rw-r--r--core/java/android/os/Vibrator.java2
-rw-r--r--core/java/android/os/vibrator/persistence/ParsedVibration.java92
-rw-r--r--core/java/android/os/vibrator/persistence/VibrationXmlParser.java214
-rw-r--r--core/java/com/android/internal/vibrator/persistence/XmlConstants.java1
-rw-r--r--core/java/com/android/internal/vibrator/persistence/XmlReader.java27
-rw-r--r--core/java/com/android/internal/vibrator/persistence/XmlValidator.java15
-rw-r--r--core/tests/vibrator/src/android/os/vibrator/persistence/ParsedVibrationTest.java117
-rw-r--r--core/tests/vibrator/src/android/os/vibrator/persistence/VibrationEffectXmlSerializationTest.java212
-rw-r--r--core/xsd/Android.bp4
-rw-r--r--core/xsd/vibrator/vibration/schema/current.txt8
-rw-r--r--core/xsd/vibrator/vibration/vibration-plus-hidden-apis.xsd10
-rw-r--r--core/xsd/vibrator/vibration/vibration.xsd10
-rw-r--r--services/core/java/com/android/server/power/ShutdownThread.java12
-rw-r--r--services/core/java/com/android/server/vibrator/HapticFeedbackCustomization.java40
-rw-r--r--services/core/java/com/android/server/vibrator/HapticFeedbackVibrationProvider.java7
-rw-r--r--services/core/java/com/android/server/vibrator/VibratorManagerService.java54
-rw-r--r--services/tests/powerservicetests/src/com/android/server/power/ShutdownThreadTest.java7
-rw-r--r--services/tests/vibrator/src/com/android/server/vibrator/HapticFeedbackCustomizationTest.java159
22 files changed, 881 insertions, 124 deletions
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 773d720d73fb..e34ddafc9e64 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -2704,8 +2704,14 @@ package android.os.vibrator {
package android.os.vibrator.persistence {
+ public class ParsedVibration {
+ method @NonNull public java.util.List<android.os.VibrationEffect> getVibrationEffectListForTesting();
+ method @Nullable public android.os.VibrationEffect resolve(@NonNull android.os.Vibrator);
+ }
+
public final class VibrationXmlParser {
- method @Nullable public static android.os.VibrationEffect parse(@NonNull java.io.Reader) throws java.io.IOException;
+ method @Nullable public static android.os.vibrator.persistence.ParsedVibration parseDocument(@NonNull java.io.Reader) throws java.io.IOException;
+ method @Nullable public static android.os.VibrationEffect parseVibrationEffect(@NonNull java.io.Reader) throws java.io.IOException;
}
public final class VibrationXmlSerializer {
diff --git a/core/java/android/hardware/input/InputDeviceVibrator.java b/core/java/android/hardware/input/InputDeviceVibrator.java
index 9c1826071822..4577e1d7d4d3 100644
--- a/core/java/android/hardware/input/InputDeviceVibrator.java
+++ b/core/java/android/hardware/input/InputDeviceVibrator.java
@@ -82,7 +82,7 @@ final class InputDeviceVibrator extends Vibrator {
}
@Override
- protected VibratorInfo getInfo() {
+ public VibratorInfo getInfo() {
return mVibratorInfo;
}
diff --git a/core/java/android/os/SystemVibrator.java b/core/java/android/os/SystemVibrator.java
index 2cda787082c7..1cd0f3b156c2 100644
--- a/core/java/android/os/SystemVibrator.java
+++ b/core/java/android/os/SystemVibrator.java
@@ -62,7 +62,7 @@ public class SystemVibrator extends Vibrator {
}
@Override
- protected VibratorInfo getInfo() {
+ public VibratorInfo getInfo() {
synchronized (mLock) {
if (mVibratorInfo != null) {
return mVibratorInfo;
diff --git a/core/java/android/os/SystemVibratorManager.java b/core/java/android/os/SystemVibratorManager.java
index eb2a712c8575..284b2464c468 100644
--- a/core/java/android/os/SystemVibratorManager.java
+++ b/core/java/android/os/SystemVibratorManager.java
@@ -194,7 +194,7 @@ public class SystemVibratorManager extends VibratorManager {
}
@Override
- protected VibratorInfo getInfo() {
+ public VibratorInfo getInfo() {
return mVibratorInfo;
}
diff --git a/core/java/android/os/Vibrator.java b/core/java/android/os/Vibrator.java
index 79e0ca87eade..aafa5018af10 100644
--- a/core/java/android/os/Vibrator.java
+++ b/core/java/android/os/Vibrator.java
@@ -153,7 +153,7 @@ public abstract class Vibrator {
*
* @hide
*/
- protected VibratorInfo getInfo() {
+ public VibratorInfo getInfo() {
return VibratorInfo.EMPTY_VIBRATOR_INFO;
}
diff --git a/core/java/android/os/vibrator/persistence/ParsedVibration.java b/core/java/android/os/vibrator/persistence/ParsedVibration.java
new file mode 100644
index 000000000000..a76f597252ec
--- /dev/null
+++ b/core/java/android/os/vibrator/persistence/ParsedVibration.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os.vibrator.persistence;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.TestApi;
+import android.os.VibrationEffect;
+import android.os.Vibrator;
+import android.os.VibratorInfo;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * The result of parsing a serialized vibration, which can be define by one or more
+ * {@link VibrationEffect} and a resolution method.
+ *
+ * @hide
+ */
+@TestApi
+public class ParsedVibration {
+ private final List<VibrationEffect> mEffects;
+
+ /** @hide */
+ public ParsedVibration(@NonNull List<VibrationEffect> effects) {
+ mEffects = effects;
+ }
+
+ /** @hide */
+ public ParsedVibration(@NonNull VibrationEffect effect) {
+ mEffects = List.of(effect);
+ }
+ /**
+ * Returns the first parsed vibration supported by {@code vibrator}, or {@code null} if none of
+ * the parsed vibrations are supported.
+ *
+ * @hide
+ */
+ @TestApi
+ @Nullable
+ public VibrationEffect resolve(@NonNull Vibrator vibrator) {
+ return resolve(vibrator.getInfo());
+ }
+
+ /**
+ * Returns the parsed vibrations for testing purposes.
+ *
+ * <p>Real callers should not use this method. Instead, they should resolve to a
+ * {@link VibrationEffect} via {@link #resolve(Vibrator)}.
+ *
+ * @hide
+ */
+ @TestApi
+ @VisibleForTesting
+ @NonNull
+ public List<VibrationEffect> getVibrationEffectListForTesting() {
+ return Collections.unmodifiableList(mEffects);
+ }
+
+ /**
+ * Same as {@link #resolve(Vibrator)}, but uses {@link VibratorInfo} instead for resolving.
+ *
+ * @hide
+ */
+ @Nullable
+ public final VibrationEffect resolve(@NonNull VibratorInfo info) {
+ for (int i = 0; i < mEffects.size(); i++) {
+ VibrationEffect effect = mEffects.get(i);
+ if (info.areVibrationFeaturesSupported(effect)) {
+ return effect;
+ }
+ }
+ return null;
+ }
+}
diff --git a/core/java/android/os/vibrator/persistence/VibrationXmlParser.java b/core/java/android/os/vibrator/persistence/VibrationXmlParser.java
index e91e04ec9cd1..e08cc4262bed 100644
--- a/core/java/android/os/vibrator/persistence/VibrationXmlParser.java
+++ b/core/java/android/os/vibrator/persistence/VibrationXmlParser.java
@@ -28,6 +28,7 @@ import com.android.internal.vibrator.persistence.VibrationEffectXmlParser;
import com.android.internal.vibrator.persistence.XmlConstants;
import com.android.internal.vibrator.persistence.XmlParserException;
import com.android.internal.vibrator.persistence.XmlReader;
+import com.android.internal.vibrator.persistence.XmlValidator;
import com.android.modules.utils.TypedXmlPullParser;
import org.xmlpull.v1.XmlPullParser;
@@ -37,11 +38,18 @@ import java.io.IOException;
import java.io.Reader;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.List;
/**
* Parses XML into a {@link VibrationEffect}.
*
- * <p>This parser supports a root element that represent a single vibration effect as follows:
+ * <p>This parser supports a root element that represent a single vibration effect or a selection
+ * list of vibration effects.
+ *
+ * <p>Use the schema at core/xsd/vibrator/vibration/vibration.xsd.
+ *
+ * <p>When the root element represents a single vibration effect, the format is as follows:
*
* * Predefined vibration effects
*
@@ -85,6 +93,26 @@ import java.lang.annotation.RetentionPolicy;
* }
* </pre>
*
+ * <p>When the root element represents a selection list of vibration effects, the root tag should be
+ * a <vibration-select> tag. The root element should contain a list of vibration serializations.
+ * Each vibration within the root-element should follow the format discussed for the <vibration> tag
+ * above. See example below:
+ *
+ * <pre>
+ * {@code
+ * <vibration-select>
+ * <vibration>
+ * <predefined-effect name="click" />
+ * </vibration>
+ * <vibration>
+ * <waveform-effect>
+ * <waveform-entry amplitude="default" durationMs="10" />
+ * </waveform-effect>
+ * </vibration>
+ * </vibration-select>
+ * }
+ * </pre>
+ *
* @hide
*/
@TestApi
@@ -140,6 +168,9 @@ public final class VibrationXmlParser {
/**
* Parses XML content from given input stream into a {@link VibrationEffect}.
*
+ * <p>This method parses an XML content that contains a single, complete {@link VibrationEffect}
+ * serialization. As such, the root tag must be a "vibration" tag.
+ *
* <p>This parser fails silently and returns {@code null} if the content of the input stream
* does not follow the schema or has unsupported values.
*
@@ -150,75 +181,106 @@ public final class VibrationXmlParser {
*/
@TestApi
@Nullable
- public static VibrationEffect parse(@NonNull Reader reader) throws IOException {
- return parse(reader, /* flags= */ 0);
+ public static VibrationEffect parseVibrationEffect(@NonNull Reader reader) throws IOException {
+ return parseVibrationEffect(reader, /* flags= */ 0);
}
/**
* Parses XML content from given input stream into a {@link VibrationEffect}.
*
- * <p>Same as {@link #parse(Reader)}, with extra flags to control the parsing behavior.
+ * <p>This method parses an XML content that contains a single, complete {@link VibrationEffect}
+ * serialization. As such, the root tag must be a "vibration" tag.
+ *
+ * <p>Same as {@link #parseVibrationEffect(Reader)}, with extra flags to control the parsing
+ * behavior.
*
* @hide
*/
@Nullable
- public static VibrationEffect parse(@NonNull Reader reader, @Flags int flags)
+ public static VibrationEffect parseVibrationEffect(@NonNull Reader reader, @Flags int flags)
throws IOException {
- TypedXmlPullParser parser = Xml.newFastPullParser();
-
try {
- parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true);
- parser.setInput(reader);
- } catch (XmlPullParserException e) {
- throw new RuntimeException("An error occurred while setting up the XML parser", e);
+ return parseDocumentInternal(
+ reader, flags, VibrationXmlParser::parseVibrationEffectInternal);
+ } catch (XmlParserException | XmlPullParserException e) {
+ Slog.w(TAG, "Error parsing vibration XML", e);
+ return null;
}
+ }
- try {
- // Ensure XML starts with expected root tag.
- XmlReader.readDocumentStartTag(parser, XmlConstants.TAG_VIBRATION);
-
- // Parse root tag as a vibration effect.
- VibrationEffect effect = parseTag(parser, flags);
-
- // Ensure XML ends after root tag is consumed.
- XmlReader.readDocumentEndTag(parser);
+ /**
+ * Parses XML content from given input stream into a {@link ParsedVibration}.
+ *
+ * <p>It supports both the "vibration" and "vibration-select" root tags.
+ * <ul>
+ * <li>If "vibration" is the root tag, the serialization provided through {@code reader}
+ * should contain a valid serialization for a single vibration.
+ * <li>If "vibration-select" is the root tag, the serialization may contain one or more
+ * valid vibration serializations.
+ * </ul>
+ *
+ * <p>After parsing, it returns a {@link ParsedVibration} that opaquely represents the parsed
+ * vibration(s), and the caller can get a concrete {@link VibrationEffect} by resolving this
+ * result to a specific vibrator.
+ *
+ * <p>This parser fails silently and returns {@code null} if the content of the input does not
+ * follow the schema or has unsupported values.
+ *
+ * @return a {@link ParsedVibration}
+ * @throws IOException error reading from given {@link Reader}
+ *
+ * @hide
+ */
+ @TestApi
+ @Nullable
+ public static ParsedVibration parseDocument(@NonNull Reader reader) throws IOException {
+ return parseDocument(reader, /* flags= */ 0);
+ }
- return effect;
- } catch (XmlParserException | VibrationXmlParserException e) {
- Slog.w(TAG, "Error parsing vibration XML", e);
+ /**
+ * Parses XML content from given input stream into a {@link ParsedVibration}.
+ *
+ * <p>Same as {@link #parseDocument(Reader)}, with extra flags to control the parsing behavior.
+ *
+ * @hide
+ */
+ @Nullable
+ public static ParsedVibration parseDocument(@NonNull Reader reader, @Flags int flags)
+ throws IOException {
+ try {
+ return parseDocumentInternal(reader, flags, VibrationXmlParser::parseElementInternal);
+ } catch (XmlParserException | XmlPullParserException e) {
+ Slog.w(TAG, "Error parsing vibration/vibration-select XML", e);
return null;
}
}
/**
- * Parses XML content from given open {@link TypedXmlPullParser} into a {@link VibrationEffect}.
+ * Parses XML content from a given open {@link TypedXmlPullParser} into a
+ * {@link ParsedVibration}.
*
- * <p>The provided parser should be pointing to a start of a valid vibration XML (i.e. to a
- * start <vibration> tag). No other parser position, including start of document, is considered
- * valid.
+ * <p>Same as {@link #parseDocument(Reader, int)}, but, instead of parsing the full XML content,
+ * it takes a parser that points to either a <vibration> or a <vibration-select> start tag. No
+ * other parser position, including start of document, is considered valid.
*
- * <p>This method parses as long as it reads a valid vibration XML, and until an end vibration
- * tag. After a successful parsing, the parser will point to the end vibration tag (i.e. to a
- * </vibration> tag).
+ * <p>This method parses until an end "vibration" or "vibration-select" tag (depending on the
+ * start tag found at the start of parsing). After a successful parsing, the parser will point
+ * to the end tag.
*
* @throws IOException error parsing from given {@link TypedXmlPullParser}.
* @throws VibrationXmlParserException if the XML tag cannot be parsed into a
- * {@link VibrationEffect}. The given {@code parser} might be pointing to a child XML tag
+ * {@link ParsedVibration}. The given {@code parser} might be pointing to a child XML tag
* that caused the parser failure.
*
* @hide
*/
@NonNull
- public static VibrationEffect parseTag(@NonNull TypedXmlPullParser parser, @Flags int flags)
+ public static ParsedVibration parseElement(@NonNull TypedXmlPullParser parser, @Flags int flags)
throws IOException, VibrationXmlParserException {
- int parserFlags = 0;
- if ((flags & VibrationXmlParser.FLAG_ALLOW_HIDDEN_APIS) != 0) {
- parserFlags |= XmlConstants.FLAG_ALLOW_HIDDEN_APIS;
- }
try {
- return VibrationEffectXmlParser.parseTag(parser, parserFlags).deserialize();
+ return parseElementInternal(parser, flags);
} catch (XmlParserException e) {
- throw new VibrationXmlParserException("Error parsing vibration effect.", e);
+ throw new VibrationXmlParserException("Error parsing vibration-select.", e);
}
}
@@ -231,6 +293,82 @@ public final class VibrationXmlParser {
private VibrationXmlParserException(String message, Throwable cause) {
super(message, cause);
}
+
+ private VibrationXmlParserException(String message) {
+ super(message);
+ }
+ }
+
+ private static ParsedVibration parseElementInternal(
+ @NonNull TypedXmlPullParser parser, @Flags int flags)
+ throws IOException, XmlParserException {
+ XmlValidator.checkStartTag(parser);
+
+ String tagName = parser.getName();
+ switch(tagName) {
+ case XmlConstants.TAG_VIBRATION:
+ return new ParsedVibration(parseVibrationEffectInternal(parser, flags));
+ case XmlConstants.TAG_VIBRATION_SELECT:
+ return parseVibrationSelectInternal(parser, flags);
+ default:
+ throw new XmlParserException(
+ "Unexpected tag name when parsing element: " + tagName);
+ }
+ }
+
+ private static ParsedVibration parseVibrationSelectInternal(
+ @NonNull TypedXmlPullParser parser, @Flags int flags)
+ throws IOException, XmlParserException {
+ XmlValidator.checkStartTag(parser, XmlConstants.TAG_VIBRATION_SELECT);
+ XmlValidator.checkTagHasNoUnexpectedAttributes(parser);
+
+ int rootDepth = parser.getDepth();
+ List<VibrationEffect> effects = new ArrayList<>();
+ while (XmlReader.readNextTagWithin(parser, rootDepth)) {
+ effects.add(parseVibrationEffectInternal(parser, flags));
+ }
+ return new ParsedVibration(effects);
+ }
+
+ /** Parses a single XML element for "vibration" tag into a {@link VibrationEffect}. */
+ private static VibrationEffect parseVibrationEffectInternal(
+ @NonNull TypedXmlPullParser parser, @Flags int flags)
+ throws IOException, XmlParserException {
+ int parserFlags = 0;
+ if ((flags & VibrationXmlParser.FLAG_ALLOW_HIDDEN_APIS) != 0) {
+ parserFlags |= XmlConstants.FLAG_ALLOW_HIDDEN_APIS;
+ }
+ return VibrationEffectXmlParser.parseTag(parser, parserFlags).deserialize();
+ }
+
+ /**
+ * This method parses a whole XML document (provided through a {@link Reader}). The root tag is
+ * parsed as per a provided {@link ElementParser}.
+ */
+ private static <T> T parseDocumentInternal(
+ @NonNull Reader reader, @Flags int flags, ElementParser<T> parseLogic)
+ throws IOException, XmlParserException, XmlPullParserException {
+ TypedXmlPullParser parser = Xml.newFastPullParser();
+ parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true);
+ parser.setInput(reader);
+
+ // Ensure XML starts with a document start tag.
+ XmlReader.readDocumentStart(parser);
+
+ // Parse root tag.
+ T result = parseLogic.parse(parser, flags);
+
+ // Ensure XML ends after root tag is consumed.
+ XmlReader.readDocumentEndTag(parser);
+
+ return result;
+ }
+
+ /** Encapsulate a logic to parse an XML element from an open parser. */
+ private interface ElementParser<T> {
+ /** Parses a single XML element starting from the current position of the {@code parser}. */
+ T parse(@NonNull TypedXmlPullParser parser, @Flags int flags)
+ throws IOException, XmlParserException;
}
private VibrationXmlParser() {
diff --git a/core/java/com/android/internal/vibrator/persistence/XmlConstants.java b/core/java/com/android/internal/vibrator/persistence/XmlConstants.java
index d1c78f0b54ea..6b69a158220c 100644
--- a/core/java/com/android/internal/vibrator/persistence/XmlConstants.java
+++ b/core/java/com/android/internal/vibrator/persistence/XmlConstants.java
@@ -36,6 +36,7 @@ public final class XmlConstants {
public static final String NAMESPACE = null;
public static final String TAG_VIBRATION = "vibration";
+ public static final String TAG_VIBRATION_SELECT = "vibration-select";
public static final String TAG_PREDEFINED_EFFECT = "predefined-effect";
public static final String TAG_PRIMITIVE_EFFECT = "primitive-effect";
diff --git a/core/java/com/android/internal/vibrator/persistence/XmlReader.java b/core/java/com/android/internal/vibrator/persistence/XmlReader.java
index 75073387c143..a5ace8438142 100644
--- a/core/java/com/android/internal/vibrator/persistence/XmlReader.java
+++ b/core/java/com/android/internal/vibrator/persistence/XmlReader.java
@@ -41,17 +41,26 @@ public final class XmlReader {
*/
public static void readDocumentStartTag(TypedXmlPullParser parser, String expectedRootTag)
throws XmlParserException, IOException {
- try {
- int type = parser.getEventType();
- checkArgument(type == XmlPullParser.START_DOCUMENT, "Document already started");
+ readDocumentStart(parser);
- type = parser.nextTag(); // skips comments, instruction tokens and whitespace only
- XmlValidator.checkParserCondition(type == XmlPullParser.START_TAG,
- "Unexpected element at document start, expected root tag %s", expectedRootTag);
+ String tagName = parser.getName();
+ XmlValidator.checkParserCondition(expectedRootTag.equals(tagName),
+ "Unexpected root tag found %s, expected %s", tagName, expectedRootTag);
+ }
- String tagName = parser.getName();
- XmlValidator.checkParserCondition(expectedRootTag.equals(tagName),
- "Unexpected root tag found %s, expected %s", tagName, expectedRootTag);
+ /**
+ * Check parser is currently at {@link XmlPullParser#START_DOCUMENT}.
+ *
+ * <p>The parser will be pointing to the first tag in the document.
+ */
+ public static void readDocumentStart(TypedXmlPullParser parser)
+ throws XmlParserException, IOException {
+ try {
+ int type = parser.getEventType();
+ checkArgument(
+ type == XmlPullParser.START_DOCUMENT,
+ "Unexpected type, expected %d", type);
+ parser.nextTag(); // skips comments, instruction tokens and whitespace only
} catch (XmlPullParserException e) {
throw XmlParserException.createFromPullParserException("document start tag", e);
}
diff --git a/core/java/com/android/internal/vibrator/persistence/XmlValidator.java b/core/java/com/android/internal/vibrator/persistence/XmlValidator.java
index ba95e35d2a35..84d4f3f49e8a 100644
--- a/core/java/com/android/internal/vibrator/persistence/XmlValidator.java
+++ b/core/java/com/android/internal/vibrator/persistence/XmlValidator.java
@@ -42,13 +42,20 @@ public final class XmlValidator {
*/
public static void checkStartTag(TypedXmlPullParser parser, String expectedTag)
throws XmlParserException {
- String tagName = parser.getName();
+ checkStartTag(parser);
+ checkParserCondition(
+ expectedTag.equals(parser.getName()),
+ "Unexpected start tag found %s, expected %s", parser.getName(), expectedTag);
+ }
+
+ /** Check parser is currently at {@link XmlPullParser#START_TAG}. */
+ public static void checkStartTag(TypedXmlPullParser parser) throws XmlParserException {
try {
checkParserCondition(
- parser.getEventType() == parser.START_TAG && expectedTag.equals(tagName),
- "Unexpected tag found %s, expected %s", tagName, expectedTag);
+ parser.getEventType() == parser.START_TAG,
+ "Expected start tag, got " + parser.getEventType());
} catch (XmlPullParserException e) {
- throw XmlParserException.createFromPullParserException(tagName, e);
+ throw XmlParserException.createFromPullParserException(parser.getName(), e);
}
}
diff --git a/core/tests/vibrator/src/android/os/vibrator/persistence/ParsedVibrationTest.java b/core/tests/vibrator/src/android/os/vibrator/persistence/ParsedVibrationTest.java
new file mode 100644
index 000000000000..274c25a17f58
--- /dev/null
+++ b/core/tests/vibrator/src/android/os/vibrator/persistence/ParsedVibrationTest.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os.vibrator.persistence;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.when;
+
+import android.os.VibrationEffect;
+import android.os.Vibrator;
+import android.os.VibratorInfo;
+
+import com.google.common.truth.Subject;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoJUnitRunner;
+import org.mockito.junit.MockitoRule;
+
+import java.util.List;
+
+/** Unit tests for {@link ParsedVibration}. */
+@RunWith(MockitoJUnitRunner.class)
+public class ParsedVibrationTest {
+
+ @Rule
+ public MockitoRule mMockitoRule = MockitoJUnit.rule();
+
+ @Mock Vibrator mVibratorMock;
+ @Mock VibratorInfo mVibratorInfoMock;
+
+ @Mock VibrationEffect mEffect1;
+ @Mock VibrationEffect mEffect2;
+ @Mock VibrationEffect mEffect3;
+
+ @Before
+ public void setUp() {
+ when(mVibratorMock.getInfo()).thenReturn(mVibratorInfoMock);
+ }
+
+ @Test
+ public void empty() {
+ assertThat(new ParsedVibration(List.of()).resolve(mVibratorMock)).isNull();
+ }
+
+ @Test
+ public void testResolve_allUnsupportedVibrations() {
+ when(mVibratorInfoMock.areVibrationFeaturesSupported(any())).thenReturn(false);
+
+ assertThatResolution(mVibratorMock, mEffect1).isNull();
+ assertThatResolution(mVibratorMock, List.of(mEffect1, mEffect2)).isNull();
+ }
+
+ @Test
+ public void testResolve_allSupportedVibrations() {
+ when(mVibratorInfoMock.areVibrationFeaturesSupported(any())).thenReturn(true);
+
+ assertThatResolution(mVibratorMock, mEffect1).isEqualTo(mEffect1);
+ assertThatResolution(mVibratorMock, List.of(mEffect1, mEffect2)).isEqualTo(mEffect1);
+ }
+
+ @Test
+ public void testResolve_mixedSupportedAndUnsupportedVibrations() {
+ when(mVibratorInfoMock.areVibrationFeaturesSupported(mEffect1)).thenReturn(true);
+ when(mVibratorInfoMock.areVibrationFeaturesSupported(mEffect2)).thenReturn(true);
+ when(mVibratorInfoMock.areVibrationFeaturesSupported(mEffect3)).thenReturn(false);
+
+ assertThatResolution(mVibratorMock, List.of(mEffect1, mEffect3)).isEqualTo(mEffect1);
+ assertThatResolution(mVibratorMock, List.of(mEffect3, mEffect1, mEffect2))
+ .isEqualTo(mEffect1);
+ assertThatResolution(mVibratorMock, List.of(mEffect1, mEffect2, mEffect3))
+ .isEqualTo(mEffect1);
+ }
+
+ @Test
+ public void testGetVibrationEffectListForTesting() {
+ ParsedVibration parsedVibration =
+ new ParsedVibration(List.of(mEffect1, mEffect2, mEffect3));
+ assertThat(parsedVibration.getVibrationEffectListForTesting())
+ .containsExactly(mEffect1, mEffect2, mEffect3)
+ .inOrder();
+
+ parsedVibration = new ParsedVibration(List.of(mEffect1));
+ assertThat(parsedVibration.getVibrationEffectListForTesting()).containsExactly(mEffect1);
+
+ parsedVibration = new ParsedVibration(List.of());
+ assertThat(parsedVibration.getVibrationEffectListForTesting()).isEmpty();
+ }
+
+ private Subject assertThatResolution(
+ Vibrator vibrator, List<VibrationEffect> componentVibrations) {
+ return assertThat(new ParsedVibration(componentVibrations).resolve(vibrator));
+ }
+
+ private Subject assertThatResolution(Vibrator vibrator, VibrationEffect vibration) {
+ return assertThat(new ParsedVibration(vibration).resolve(vibrator));
+ }
+}
diff --git a/core/tests/vibrator/src/android/os/vibrator/persistence/VibrationEffectXmlSerializationTest.java b/core/tests/vibrator/src/android/os/vibrator/persistence/VibrationEffectXmlSerializationTest.java
index ce1717057a1c..d73b5cb5713b 100644
--- a/core/tests/vibrator/src/android/os/vibrator/persistence/VibrationEffectXmlSerializationTest.java
+++ b/core/tests/vibrator/src/android/os/vibrator/persistence/VibrationEffectXmlSerializationTest.java
@@ -42,6 +42,7 @@ import java.io.StringReader;
import java.io.StringWriter;
import java.util.HashMap;
import java.util.Map;
+import java.util.Set;
/**
* Unit tests for {@link VibrationXmlParser} and {@link VibrationXmlSerializer}.
@@ -67,7 +68,8 @@ public class VibrationEffectXmlSerializationTest {
}
@Test
- public void testParseTag_succeedAndParserPointsToEndVibrationTag() throws Exception {
+ public void testParseElement_fromVibrationTag_succeedAndParserPointsToEndVibrationTag()
+ throws Exception {
VibrationEffect effect = VibrationEffect.startComposition()
.addPrimitive(PRIMITIVE_CLICK)
.addPrimitive(PRIMITIVE_TICK, 0.2497f)
@@ -86,7 +88,7 @@ public class VibrationEffectXmlSerializationTest {
+ "</vibration>";
TypedXmlPullParser parser = createXmlPullParser(xml);
- assertParseTagSucceeds(parser, effect);
+ assertParseElementSucceeds(parser, effect);
parser.next();
assertEndOfDocument(parser);
@@ -95,30 +97,134 @@ public class VibrationEffectXmlSerializationTest {
parser = createXmlPullParser("<next-tag>" + xml + "</next-tag>");
// Move the parser once to point to the "<vibration> tag.
parser.next();
- assertParseTagSucceeds(parser, effect);
+ assertParseElementSucceeds(parser, effect);
parser.next();
assertEndTag(parser, "next-tag");
parser = createXmlPullParser(xml + "<next-tag>");
- assertParseTagSucceeds(parser, effect);
+ assertParseElementSucceeds(parser, effect);
parser.next();
assertStartTag(parser, "next-tag");
parser = createXmlPullParser(xml + xml2);
- assertParseTagSucceeds(parser, effect);
+ assertParseElementSucceeds(parser, effect);
+ parser.next();
+ assertParseElementSucceeds(parser, effect2);
+ parser.next();
+ assertEndOfDocument(parser);
+
+ // Check when there is comment before the end tag.
+ xml = "<vibration><primitive-effect name=\"tick\"/><!-- comment --></vibration>";
+ parser = createXmlPullParser(xml);
+ assertParseElementSucceeds(
+ parser, VibrationEffect.startComposition().addPrimitive(PRIMITIVE_TICK).compose());
+ }
+
+ @Test
+ public void
+ testParseElement_fromVibrationSelectTag_succeedAndParserPointsToEndVibrationSelectTag()
+ throws Exception {
+ VibrationEffect effect1 = VibrationEffect.startComposition()
+ .addPrimitive(PRIMITIVE_CLICK)
+ .addPrimitive(PRIMITIVE_TICK, 0.2497f)
+ .compose();
+ String vibrationXml1 = "<vibration>"
+ + "<primitive-effect name=\"click\"/>"
+ + "<primitive-effect name=\"tick\" scale=\"0.2497\"/>"
+ + "</vibration>";
+ VibrationEffect effect2 = VibrationEffect.startComposition()
+ .addPrimitive(PRIMITIVE_LOW_TICK, 1f, 356)
+ .addPrimitive(PRIMITIVE_SPIN, 0.6364f, 7)
+ .compose();
+ String vibrationXml2 = "<vibration>"
+ + "<primitive-effect name=\"low_tick\" delayMs=\"356\"/>"
+ + "<primitive-effect name=\"spin\" scale=\"0.6364\" delayMs=\"7\"/>"
+ + "</vibration>";
+
+ String xml = "<vibration-select>" + vibrationXml1 + vibrationXml2 + "</vibration-select>";
+ TypedXmlPullParser parser = createXmlPullParser(xml);
+ assertParseElementSucceeds(parser, effect1, effect2);
+ parser.next();
+ assertEndOfDocument(parser);
+
+ // Test no-issues when an end-tag follows the vibration XML.
+ // To test this, starting with the corresponding "start-tag" is necessary.
+ parser = createXmlPullParser("<next-tag>" + xml + "</next-tag>");
+ // Move the parser once to point to the "<vibration> tag.
+ parser.next();
+ assertParseElementSucceeds(parser, effect1, effect2);
+ parser.next();
+ assertEndTag(parser, "next-tag");
+
+ parser = createXmlPullParser(xml + "<next-tag>");
+ assertParseElementSucceeds(parser, effect1, effect2);
parser.next();
- assertParseTagSucceeds(parser, effect2);
+ assertStartTag(parser, "next-tag");
+
+ xml = "<vibration-select>" + vibrationXml1 + vibrationXml2 + "</vibration-select>"
+ + "<vibration-select>" + vibrationXml2 + vibrationXml1 + "</vibration-select>"
+ + vibrationXml1;
+ parser = createXmlPullParser(xml);
+ assertParseElementSucceeds(parser, effect1, effect2);
+ parser.next();
+ assertParseElementSucceeds(parser, effect2, effect1);
+ parser.next();
+ assertParseElementSucceeds(parser, effect1);
parser.next();
assertEndOfDocument(parser);
+
+ // Check when there is comment before the end tag.
+ xml = "<vibration-select>" + vibrationXml1 + "<!-- comment --></vibration-select>";
+ parser = createXmlPullParser(xml);
+ parser.next();
+ assertParseElementSucceeds(parser, effect1);
+ }
+
+ @Test
+ public void testParseElement_withHiddenApis_onlySucceedsWithFlag() throws Exception {
+ // Check when the root tag is "vibration".
+ String xml = "<vibration><predefined-effect name=\"texture_tick\"/></vibration>";
+ assertParseElementSucceeds(createXmlPullParser(xml),
+ VibrationXmlSerializer.FLAG_ALLOW_HIDDEN_APIS,
+ VibrationEffect.get(VibrationEffect.EFFECT_TEXTURE_TICK));
+ assertParseElementFails(xml);
+
+ // Check when the root tag is "vibration-select".
+ xml = "<vibration-select>" + xml + "</vibration-select>";
+ assertParseElementSucceeds(createXmlPullParser(xml),
+ VibrationXmlSerializer.FLAG_ALLOW_HIDDEN_APIS,
+ VibrationEffect.get(VibrationEffect.EFFECT_TEXTURE_TICK));
+ assertParseElementFails(xml);
}
@Test
- public void testParseTag_badXml_throwsException() throws Exception {
- assertParseTagFails(
+ public void testParseElement_badXml_throwsException() throws Exception {
+ // No "vibration-select" tag.
+ assertParseElementFails(
"<vibration>random text<primitive-effect name=\"click\"/></vibration>");
- assertParseTagFails("<bad-tag><primitive-effect name=\"click\"/></vibration>");
- assertParseTagFails("<primitive-effect name=\"click\"/></vibration>");
- assertParseTagFails("<vibration><primitive-effect name=\"click\"/>");
+ assertParseElementFails("<bad-tag><primitive-effect name=\"click\"/></vibration>");
+ assertParseElementFails("<primitive-effect name=\"click\"/></vibration>");
+ assertParseElementFails("<vibration><primitive-effect name=\"click\"/>");
+
+ // Incomplete XML.
+ assertParseElementFails("<vibration-select><primitive-effect name=\"click\"/>");
+ assertParseElementFails("<vibration-select>"
+ + "<vibration>"
+ + "<primitive-effect name=\"low_tick\" delayMs=\"356\"/>"
+ + "</vibration>");
+
+ // Bad vibration XML.
+ assertParseElementFails("<vibration-select>"
+ + "<primitive-effect name=\"low_tick\" delayMs=\"356\"/>"
+ + "</vibration>"
+ + "</vibration-select>");
+
+ // "vibration-select" tag should have no attributes.
+ assertParseElementFails("<vibration-select bad_attr=\"123\">"
+ + "<vibration>"
+ + "<predefined-effect name=\"tick\"/>"
+ + "</vibration>"
+ + "</vibration-select>");
}
@Test
@@ -140,12 +246,30 @@ public class VibrationEffectXmlSerializationTest {
assertPublicApisSerializerSucceeds(effect, "click", "tick", "low_tick", "spin");
assertPublicApisRoundTrip(effect);
- assertHiddenApisParserSucceeds(xml, effect);
+ assertHiddenApisParseVibrationEffectSucceeds(xml, effect);
assertHiddenApisSerializerSucceeds(effect, "click", "tick", "low_tick", "spin");
assertHiddenApisRoundTrip(effect);
}
@Test
+ public void testParseDocument_withVibrationSelectTag_withHiddenApis_onlySucceedsWithFlag()
+ throws Exception {
+ // Check when the root tag is "vibration".
+ String xml = "<vibration><predefined-effect name=\"texture_tick\"/></vibration>";
+ assertParseDocumentSucceeds(xml,
+ VibrationXmlSerializer.FLAG_ALLOW_HIDDEN_APIS,
+ VibrationEffect.get(VibrationEffect.EFFECT_TEXTURE_TICK));
+ assertThat(parseDocument(xml, /* flags= */ 0)).isNull();
+
+ // Check when the root tag is "vibration-select".
+ xml = "<vibration-select>" + xml + "</vibration-select>";
+ assertParseDocumentSucceeds(xml,
+ VibrationXmlSerializer.FLAG_ALLOW_HIDDEN_APIS,
+ VibrationEffect.get(VibrationEffect.EFFECT_TEXTURE_TICK));
+ assertThat(parseDocument(xml, /* flags= */ 0)).isNull();
+ }
+
+ @Test
public void testWaveforms_allSucceed() throws IOException {
VibrationEffect effect = VibrationEffect.createWaveform(new long[]{123, 456, 789, 0},
new int[]{254, 1, 255, 0}, /* repeat= */ 0);
@@ -162,7 +286,7 @@ public class VibrationEffectXmlSerializationTest {
assertPublicApisSerializerSucceeds(effect, "123", "456", "789", "254", "1", "255", "0");
assertPublicApisRoundTrip(effect);
- assertHiddenApisParserSucceeds(xml, effect);
+ assertHiddenApisParseVibrationEffectSucceeds(xml, effect);
assertHiddenApisSerializerSucceeds(effect, "123", "456", "789", "254", "1", "255", "0");
assertHiddenApisRoundTrip(effect);
}
@@ -179,7 +303,7 @@ public class VibrationEffectXmlSerializationTest {
assertPublicApisSerializerSucceeds(effect, entry.getKey());
assertPublicApisRoundTrip(effect);
- assertHiddenApisParserSucceeds(xml, effect);
+ assertHiddenApisParseVibrationEffectSucceeds(xml, effect);
assertHiddenApisSerializerSucceeds(effect, entry.getKey());
assertHiddenApisRoundTrip(effect);
}
@@ -195,7 +319,7 @@ public class VibrationEffectXmlSerializationTest {
assertPublicApisParserFails(xml);
assertPublicApisSerializerFails(effect);
- assertHiddenApisParserSucceeds(xml, effect);
+ assertHiddenApisParseVibrationEffectSucceeds(xml, effect);
assertHiddenApisSerializerSucceeds(effect, entry.getKey());
assertHiddenApisRoundTrip(effect);
}
@@ -214,19 +338,19 @@ public class VibrationEffectXmlSerializationTest {
assertPublicApisParserFails(xml);
assertPublicApisSerializerFails(effect);
- assertHiddenApisParserSucceeds(xml, effect);
+ assertHiddenApisParseVibrationEffectSucceeds(xml, effect);
assertHiddenApisSerializerSucceeds(effect, entry.getKey());
assertHiddenApisRoundTrip(effect);
}
}
private void assertPublicApisParserFails(String xml) throws IOException {
- assertThat(parse(xml, /* flags= */ 0)).isNull();
+ assertThat(parseVibrationEffect(xml, /* flags= */ 0)).isNull();
}
private void assertPublicApisParserSucceeds(String xml, VibrationEffect effect)
throws IOException {
- assertThat(parse(xml, /* flags= */ 0)).isEqualTo(effect);
+ assertThat(parseVibrationEffect(xml, /* flags= */ 0)).isEqualTo(effect);
}
private TypedXmlPullParser createXmlPullParser(String xml) throws Exception {
@@ -237,16 +361,30 @@ public class VibrationEffectXmlSerializationTest {
return parser;
}
+ private void assertParseDocumentSucceeds(String xml, int flags, VibrationEffect... effects)
+ throws Exception {
+ assertThat(parseDocument(xml, flags).getVibrationEffectListForTesting())
+ .containsExactly(effects);
+ }
+
/**
* Asserts parsing vibration from an open TypedXmlPullParser succeeds, and that the parser
- * points to the end "vibration" tag.
+ * points to the end "vibration" or "vibration-select" tag.
*/
- private void assertParseTagSucceeds(
- TypedXmlPullParser parser, VibrationEffect effect) throws Exception {
- assertThat(parseTag(parser)).isEqualTo(effect);
+ private void assertParseElementSucceeds(
+ TypedXmlPullParser parser, VibrationEffect... effects) throws Exception {
+ assertParseElementSucceeds(parser, VibrationXmlParser.FLAG_ALLOW_HIDDEN_APIS, effects);
+ }
+ private void assertParseElementSucceeds(
+ TypedXmlPullParser parser, int flags, VibrationEffect... effects) throws Exception {
+ String tagName = parser.getName();
+ assertThat(Set.of("vibration", "vibration-select")).contains(tagName);
+
+ assertThat(parseElement(parser, flags).getVibrationEffectListForTesting())
+ .containsExactly(effects);
assertThat(parser.getEventType()).isEqualTo(XmlPullParser.END_TAG);
- assertThat(parser.getName()).isEqualTo("vibration");
+ assertThat(parser.getName()).isEqualTo(tagName);
}
private void assertEndTag(TypedXmlPullParser parser, String expectedTagName) throws Exception {
@@ -264,9 +402,10 @@ public class VibrationEffectXmlSerializationTest {
assertThat(parser.getEventType()).isEqualTo(parser.END_DOCUMENT);
}
- private void assertHiddenApisParserSucceeds(String xml, VibrationEffect effect)
+ private void assertHiddenApisParseVibrationEffectSucceeds(String xml, VibrationEffect effect)
throws IOException {
- assertThat(parse(xml, VibrationXmlParser.FLAG_ALLOW_HIDDEN_APIS)).isEqualTo(effect);
+ assertThat(parseVibrationEffect(xml, VibrationXmlParser.FLAG_ALLOW_HIDDEN_APIS))
+ .isEqualTo(effect);
}
private void assertPublicApisSerializerFails(VibrationEffect effect) {
@@ -275,10 +414,10 @@ public class VibrationEffectXmlSerializationTest {
() -> serialize(effect, /* flags= */ 0));
}
- private void assertParseTagFails(String xml) {
+ private void assertParseElementFails(String xml) {
assertThrows("Expected parsing to fail for " + xml,
VibrationXmlParser.VibrationXmlParserException.class,
- () -> parseTag(createXmlPullParser(xml)));
+ () -> parseElement(createXmlPullParser(xml), /* flags= */ 0));
}
private void assertPublicApisSerializerSucceeds(VibrationEffect effect,
@@ -299,22 +438,29 @@ public class VibrationEffectXmlSerializationTest {
}
private void assertPublicApisRoundTrip(VibrationEffect effect) throws IOException {
- assertThat(parse(serialize(effect, /* flags= */ 0), /* flags= */ 0)).isEqualTo(effect);
+ assertThat(parseVibrationEffect(serialize(effect, /* flags= */ 0), /* flags= */ 0))
+ .isEqualTo(effect);
}
private void assertHiddenApisRoundTrip(VibrationEffect effect) throws IOException {
String xml = serialize(effect, VibrationXmlSerializer.FLAG_ALLOW_HIDDEN_APIS);
- assertThat(parse(xml, VibrationXmlParser.FLAG_ALLOW_HIDDEN_APIS)).isEqualTo(effect);
+ assertThat(parseVibrationEffect(xml, VibrationXmlParser.FLAG_ALLOW_HIDDEN_APIS))
+ .isEqualTo(effect);
+ }
+
+ private static VibrationEffect parseVibrationEffect(
+ String xml, @VibrationXmlParser.Flags int flags) throws IOException {
+ return VibrationXmlParser.parseVibrationEffect(new StringReader(xml), flags);
}
- private static VibrationEffect parse(String xml, @VibrationXmlParser.Flags int flags)
+ private static ParsedVibration parseDocument(String xml, int flags)
throws IOException {
- return VibrationXmlParser.parse(new StringReader(xml), flags);
+ return VibrationXmlParser.parseDocument(new StringReader(xml), flags);
}
- private static VibrationEffect parseTag(TypedXmlPullParser parser)
+ private static ParsedVibration parseElement(TypedXmlPullParser parser, int flags)
throws IOException, VibrationXmlParser.VibrationXmlParserException {
- return VibrationXmlParser.parseTag(parser, VibrationXmlParser.FLAG_ALLOW_HIDDEN_APIS);
+ return VibrationXmlParser.parseElement(parser, flags);
}
private static String serialize(VibrationEffect effect, @VibrationXmlSerializer.Flags int flags)
diff --git a/core/xsd/Android.bp b/core/xsd/Android.bp
index f49a159585a5..4e418d6cc0f0 100644
--- a/core/xsd/Android.bp
+++ b/core/xsd/Android.bp
@@ -19,4 +19,8 @@ xsd_config {
srcs: ["vibrator/vibration/vibration.xsd"],
api_dir: "vibrator/vibration/schema",
package_name: "com.android.internal.vibrator.persistence",
+ root_elements: [
+ "vibration",
+ "vibration-select",
+ ],
}
diff --git a/core/xsd/vibrator/vibration/schema/current.txt b/core/xsd/vibrator/vibration/schema/current.txt
index 121a2285d7f6..176638462397 100644
--- a/core/xsd/vibrator/vibration/schema/current.txt
+++ b/core/xsd/vibrator/vibration/schema/current.txt
@@ -47,6 +47,11 @@ package com.android.internal.vibrator.persistence {
method public void setWaveformEffect_optional(com.android.internal.vibrator.persistence.WaveformEffect);
}
+ public class VibrationSelect {
+ ctor public VibrationSelect();
+ method public java.util.List<com.android.internal.vibrator.persistence.Vibration> getVibration();
+ }
+
public enum WaveformAmplitudeDefault {
method public String getRawName();
enum_constant public static final com.android.internal.vibrator.persistence.WaveformAmplitudeDefault _default;
@@ -74,8 +79,9 @@ package com.android.internal.vibrator.persistence {
public class XmlParser {
ctor public XmlParser();
- method public static com.android.internal.vibrator.persistence.Vibration read(java.io.InputStream) throws javax.xml.datatype.DatatypeConfigurationException, java.io.IOException, org.xmlpull.v1.XmlPullParserException;
method public static String readText(org.xmlpull.v1.XmlPullParser) throws java.io.IOException, org.xmlpull.v1.XmlPullParserException;
+ method public static com.android.internal.vibrator.persistence.Vibration readVibration(java.io.InputStream) throws javax.xml.datatype.DatatypeConfigurationException, java.io.IOException, org.xmlpull.v1.XmlPullParserException;
+ method public static com.android.internal.vibrator.persistence.VibrationSelect readVibrationSelect(java.io.InputStream) throws javax.xml.datatype.DatatypeConfigurationException, java.io.IOException, org.xmlpull.v1.XmlPullParserException;
method public static void skip(org.xmlpull.v1.XmlPullParser) throws java.io.IOException, org.xmlpull.v1.XmlPullParserException;
}
diff --git a/core/xsd/vibrator/vibration/vibration-plus-hidden-apis.xsd b/core/xsd/vibrator/vibration/vibration-plus-hidden-apis.xsd
index cca1359da596..679b9fa019ac 100644
--- a/core/xsd/vibrator/vibration/vibration-plus-hidden-apis.xsd
+++ b/core/xsd/vibrator/vibration/vibration-plus-hidden-apis.xsd
@@ -23,10 +23,20 @@
elementFormDefault="qualified"
xmlns:xs="http://www.w3.org/2001/XMLSchema">
+ <!-- Root tag definitions -->
+
<xs:element name="vibration" type="Vibration"/>
+ <xs:element name="vibration-select" type="VibrationSelect"/>
+
<!-- Type definitions -->
+ <xs:complexType name="VibrationSelect">
+ <xs:sequence>
+ <xs:element name="vibration" type="Vibration" minOccurs="0" maxOccurs="unbounded" />
+ </xs:sequence>
+ </xs:complexType>
+
<xs:complexType name="Vibration">
<xs:choice>
diff --git a/core/xsd/vibrator/vibration/vibration.xsd b/core/xsd/vibrator/vibration/vibration.xsd
index b1a815a5eb8a..8406562b8579 100644
--- a/core/xsd/vibrator/vibration/vibration.xsd
+++ b/core/xsd/vibrator/vibration/vibration.xsd
@@ -21,10 +21,20 @@
elementFormDefault="qualified"
xmlns:xs="http://www.w3.org/2001/XMLSchema">
+ <!-- Root tag definitions -->
+
<xs:element name="vibration" type="Vibration"/>
+ <xs:element name="vibration-select" type="VibrationSelect"/>
+
<!-- Type definitions -->
+ <xs:complexType name="VibrationSelect">
+ <xs:sequence>
+ <xs:element name="vibration" type="Vibration" minOccurs="0" maxOccurs="unbounded" />
+ </xs:sequence>
+ </xs:complexType>
+
<xs:complexType name="Vibration">
<xs:choice>
diff --git a/services/core/java/com/android/server/power/ShutdownThread.java b/services/core/java/com/android/server/power/ShutdownThread.java
index 862948e3f41d..27811e9567af 100644
--- a/services/core/java/com/android/server/power/ShutdownThread.java
+++ b/services/core/java/com/android/server/power/ShutdownThread.java
@@ -851,9 +851,10 @@ public final class ShutdownThread extends Thread {
*/
private VibrationEffect getValidShutdownVibration(Context context, Vibrator vibrator) {
VibrationEffect parsedEffect = parseVibrationEffectFromFile(
- mInjector.getDefaultShutdownVibrationEffectFilePath(context));
+ mInjector.getDefaultShutdownVibrationEffectFilePath(context),
+ vibrator);
- if (parsedEffect == null || !vibrator.areVibrationFeaturesSupported(parsedEffect)) {
+ if (parsedEffect == null) {
return createDefaultVibrationEffect();
}
@@ -869,11 +870,12 @@ public final class ShutdownThread extends Thread {
return parsedEffect;
}
- private static VibrationEffect parseVibrationEffectFromFile(String filePath) {
+ private static VibrationEffect parseVibrationEffectFromFile(
+ String filePath, Vibrator vibrator) {
if (!TextUtils.isEmpty(filePath)) {
try {
- return VibrationXmlParser.parse(new FileReader(filePath));
- } catch (IOException e) {
+ return VibrationXmlParser.parseDocument(new FileReader(filePath)).resolve(vibrator);
+ } catch (Exception e) {
Log.e(TAG, "Error parsing default shutdown vibration effect.", e);
}
}
diff --git a/services/core/java/com/android/server/vibrator/HapticFeedbackCustomization.java b/services/core/java/com/android/server/vibrator/HapticFeedbackCustomization.java
index 8be3b2de4adf..3fb845f064e3 100644
--- a/services/core/java/com/android/server/vibrator/HapticFeedbackCustomization.java
+++ b/services/core/java/com/android/server/vibrator/HapticFeedbackCustomization.java
@@ -19,6 +19,8 @@ package com.android.server.vibrator;
import android.annotation.Nullable;
import android.content.res.Resources;
import android.os.VibrationEffect;
+import android.os.Vibrator;
+import android.os.vibrator.persistence.ParsedVibration;
import android.os.vibrator.persistence.VibrationXmlParser;
import android.text.TextUtils;
import android.util.Slog;
@@ -105,10 +107,10 @@ final class HapticFeedbackCustomization {
* @hide
*/
@Nullable
- static SparseArray<VibrationEffect> loadVibrations(Resources res)
+ static SparseArray<VibrationEffect> loadVibrations(Resources res, Vibrator vibrator)
throws CustomizationParserException, IOException {
try {
- return loadVibrationsInternal(res);
+ return loadVibrationsInternal(res, vibrator);
} catch (VibrationXmlParser.VibrationXmlParserException
| XmlParserException
| XmlPullParserException e) {
@@ -118,12 +120,13 @@ final class HapticFeedbackCustomization {
}
@Nullable
- private static SparseArray<VibrationEffect> loadVibrationsInternal(Resources res) throws
- CustomizationParserException,
- IOException,
- VibrationXmlParser.VibrationXmlParserException,
- XmlParserException,
- XmlPullParserException {
+ private static SparseArray<VibrationEffect> loadVibrationsInternal(
+ Resources res, Vibrator vibrator) throws
+ CustomizationParserException,
+ IOException,
+ VibrationXmlParser.VibrationXmlParserException,
+ XmlParserException,
+ XmlPullParserException {
String customizationFile =
res.getString(
com.android.internal.R.string.config_hapticFeedbackCustomizationFile);
@@ -164,16 +167,23 @@ final class HapticFeedbackCustomization {
// Move the parser one step into the `<constant>` tag.
XmlValidator.checkParserCondition(
XmlReader.readNextTagWithin(parser, customizationDepth),
- "Unsupported empty customization tag");
+ "Unsupported empty customization tag for effect " + effectId);
- VibrationEffect effect = VibrationXmlParser.parseTag(
+ ParsedVibration parsedVibration = VibrationXmlParser.parseElement(
parser, VibrationXmlParser.FLAG_ALLOW_HIDDEN_APIS);
- if (effect.getDuration() == Long.MAX_VALUE) {
- throw new CustomizationParserException(String.format(
- "Vibration for effect ID %d is repeating, which is not allowed as a"
- + " haptic feedback: %s", effectId, effect));
+ if (parsedVibration == null) {
+ throw new CustomizationParserException(
+ "Unable to parse vibration element for effect " + effectId);
+ }
+ VibrationEffect effect = parsedVibration.resolve(vibrator);
+ if (effect != null) {
+ if (effect.getDuration() == Long.MAX_VALUE) {
+ throw new CustomizationParserException(String.format(
+ "Vibration for effect ID %d is repeating, which is not allowed as a"
+ + " haptic feedback: %s", effectId, effect));
+ }
+ mapping.put(effectId, effect);
}
- mapping.put(effectId, effect);
XmlReader.readEndTag(parser, TAG_CONSTANT, customizationDepth);
}
diff --git a/services/core/java/com/android/server/vibrator/HapticFeedbackVibrationProvider.java b/services/core/java/com/android/server/vibrator/HapticFeedbackVibrationProvider.java
index 19dd0b26c50f..7c9954391d8c 100644
--- a/services/core/java/com/android/server/vibrator/HapticFeedbackVibrationProvider.java
+++ b/services/core/java/com/android/server/vibrator/HapticFeedbackVibrationProvider.java
@@ -58,7 +58,7 @@ public final class HapticFeedbackVibrationProvider {
/** @hide */
public HapticFeedbackVibrationProvider(Resources res, Vibrator vibrator) {
- this(res, vibrator, loadHapticCustomizations(res));
+ this(res, vibrator, loadHapticCustomizations(res, vibrator));
}
/** @hide */
@@ -288,9 +288,10 @@ public final class HapticFeedbackVibrationProvider {
}
@Nullable
- private static SparseArray<VibrationEffect> loadHapticCustomizations(Resources res) {
+ private static SparseArray<VibrationEffect> loadHapticCustomizations(
+ Resources res, Vibrator vibrator) {
try {
- return HapticFeedbackCustomization.loadVibrations(res);
+ return HapticFeedbackCustomization.loadVibrations(res, vibrator);
} catch (IOException | HapticFeedbackCustomization.CustomizationParserException e) {
Slog.e(TAG, "Unable to load haptic customizations.", e);
return null;
diff --git a/services/core/java/com/android/server/vibrator/VibratorManagerService.java b/services/core/java/com/android/server/vibrator/VibratorManagerService.java
index 2fef09299494..e296c7b764e5 100644
--- a/services/core/java/com/android/server/vibrator/VibratorManagerService.java
+++ b/services/core/java/com/android/server/vibrator/VibratorManagerService.java
@@ -54,6 +54,8 @@ import android.os.VibrationEffect;
import android.os.VibratorInfo;
import android.os.vibrator.PrebakedSegment;
import android.os.vibrator.VibrationEffectSegment;
+import android.os.vibrator.VibratorInfoFactory;
+import android.os.vibrator.persistence.ParsedVibration;
import android.os.vibrator.persistence.VibrationXmlParser;
import android.text.TextUtils;
import android.util.IndentingPrintWriter;
@@ -158,6 +160,9 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
private final InputDeviceDelegate mInputDeviceDelegate;
private final DeviceAdapter mDeviceAdapter;
+ @GuardedBy("mLock")
+ @Nullable private VibratorInfo mCombinedVibratorInfo;
+
private BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
@@ -1826,6 +1831,36 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
}
}
+ private VibratorInfo getCombinedVibratorInfo() {
+ synchronized (mLock) {
+ // Used a cached resolving vibrator if one exists.
+ if (mCombinedVibratorInfo != null) {
+ return mCombinedVibratorInfo;
+ }
+
+ // Return an empty resolving vibrator if the service has no vibrator.
+ if (mVibratorIds.length == 0) {
+ return mCombinedVibratorInfo = VibratorInfo.EMPTY_VIBRATOR_INFO;
+ }
+
+ // Combine the vibrator infos of all the service's vibrator to create a single resolving
+ // vibrator that is based on the combined info.
+ VibratorInfo[] infos = new VibratorInfo[mVibratorIds.length];
+ for (int i = 0; i < mVibratorIds.length; i++) {
+ VibratorInfo info = getVibratorInfo(mVibratorIds[i]);
+ // If any one of the service's vibrator does not have a valid vibrator info, stop
+ // trying to create and cache a combined resolving vibrator. Combine the infos only
+ // when infos for all vibrators are available.
+ if (info == null) {
+ return null;
+ }
+ infos[i] = info;
+ }
+
+ return mCombinedVibratorInfo = VibratorInfoFactory.create(/* id= */ -1, infos);
+ }
+ }
+
/** Implementation of {@link IExternalVibratorService} to be triggered on external control. */
@VisibleForTesting
final class ExternalVibratorService extends IExternalVibratorService.Stub {
@@ -2308,10 +2343,21 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
private CombinedVibration parseXml(String xml) {
try {
- VibrationEffect effect = VibrationXmlParser.parse(new StringReader(xml));
- if (effect == null) {
+ ParsedVibration parsedVibration =
+ VibrationXmlParser.parseDocument(new StringReader(xml));
+ if (parsedVibration == null) {
throw new IllegalArgumentException("Error parsing vibration XML " + xml);
}
+ VibratorInfo combinedVibratorInfo = getCombinedVibratorInfo();
+ if (combinedVibratorInfo == null) {
+ throw new IllegalStateException(
+ "No combined vibrator info to parse vibration XML " + xml);
+ }
+ VibrationEffect effect = parsedVibration.resolve(combinedVibratorInfo);
+ if (effect == null) {
+ throw new IllegalArgumentException(
+ "Parsed vibration cannot be resolved for vibration XML " + xml);
+ }
return CombinedVibration.createParallel(effect);
} catch (IOException e) {
throw new RuntimeException("Error parsing vibration XML " + xml, e);
@@ -2335,6 +2381,10 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
pw.println(" sequential [options] (-v <vibrator-id> <effect>...)...");
pw.println(" Vibrates different effects on each vibrator in sequence.");
pw.println(" xml [options] <xml>");
+ pw.println(" Vibrates using combined vibration described in given XML string");
+ pw.println(" on all vibrators in sync. The XML could be:");
+ pw.println(" XML containing a single effect, or");
+ pw.println(" A vibration select XML containing multiple effects.");
pw.println(" Vibrates using combined vibration described in given XML string.");
pw.println(" XML containing a single effect it runs on all vibrators in sync.");
pw.println(" cancel");
diff --git a/services/tests/powerservicetests/src/com/android/server/power/ShutdownThreadTest.java b/services/tests/powerservicetests/src/com/android/server/power/ShutdownThreadTest.java
index 6041e916ffc0..2d1b545a9e6d 100644
--- a/services/tests/powerservicetests/src/com/android/server/power/ShutdownThreadTest.java
+++ b/services/tests/powerservicetests/src/com/android/server/power/ShutdownThreadTest.java
@@ -30,6 +30,7 @@ import android.content.Context;
import android.os.VibrationAttributes;
import android.os.VibrationEffect;
import android.os.Vibrator;
+import android.os.VibratorInfo;
import android.util.AtomicFile;
import androidx.test.InstrumentationRegistry;
@@ -84,6 +85,7 @@ public class ShutdownThreadTest {
@Mock private Context mContextMock;
@Mock private Vibrator mVibratorMock;
+ @Mock private VibratorInfo mVibratorInfoMock;
private String mDefaultShutdownVibrationFilePath;
private long mLastSleepDurationMs;
@@ -94,8 +96,9 @@ public class ShutdownThreadTest {
public void setUp() {
MockitoAnnotations.initMocks(this);
when(mVibratorMock.hasVibrator()).thenReturn(true);
+ when(mVibratorMock.getInfo()).thenReturn(mVibratorInfoMock);
- when(mVibratorMock.areVibrationFeaturesSupported(any())).thenReturn(true);
+ when(mVibratorInfoMock.areVibrationFeaturesSupported(any())).thenReturn(true);
mShutdownThread = new ShutdownThread(new TestInjector());
}
@@ -130,7 +133,7 @@ public class ShutdownThreadTest {
@Test
public void testVibratorUnsupportedShutdownVibrationEffect() throws Exception {
setShutdownVibrationFileContent(WAVEFORM_VIB_10MS_SERIALIZATION);
- when(mVibratorMock.areVibrationFeaturesSupported(any())).thenReturn(false);
+ when(mVibratorInfoMock.areVibrationFeaturesSupported(any())).thenReturn(false);
mShutdownThread.playShutdownVibration(mContextMock);
diff --git a/services/tests/vibrator/src/com/android/server/vibrator/HapticFeedbackCustomizationTest.java b/services/tests/vibrator/src/com/android/server/vibrator/HapticFeedbackCustomizationTest.java
index a81898df9235..10b49c67e8bb 100644
--- a/services/tests/vibrator/src/com/android/server/vibrator/HapticFeedbackCustomizationTest.java
+++ b/services/tests/vibrator/src/com/android/server/vibrator/HapticFeedbackCustomizationTest.java
@@ -25,10 +25,13 @@ import static com.android.server.vibrator.HapticFeedbackCustomization.Customizat
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertThrows;
+import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.when;
import android.content.res.Resources;
import android.os.VibrationEffect;
+import android.os.Vibrator;
+import android.os.VibratorInfo;
import android.util.AtomicFile;
import android.util.SparseArray;
@@ -36,6 +39,7 @@ import androidx.test.InstrumentationRegistry;
import com.android.internal.R;
+import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.mockito.Mock;
@@ -60,7 +64,23 @@ public class HapticFeedbackCustomizationTest {
private static final VibrationEffect PREDEFINED_VIBRATION =
VibrationEffect.createPredefined(EFFECT_CLICK);
+ private static final String WAVEFORM_VIBRATION_XML = "<vibration>"
+ + "<waveform-effect>"
+ + "<waveform-entry durationMs=\"123\" amplitude=\"254\"/>"
+ + "</waveform-effect>"
+ + "</vibration>";
+ private static final VibrationEffect WAVEFORM_VIBARTION =
+ VibrationEffect.createWaveform(new long[] {123}, new int[] {254}, -1);
+
@Mock private Resources mResourcesMock;
+ @Mock private Vibrator mVibratorMock;
+ @Mock private VibratorInfo mVibratorInfoMock;
+
+ @Before
+ public void setUp() {
+ when(mVibratorMock.getInfo()).thenReturn(mVibratorInfoMock);
+ when(mVibratorInfoMock.areVibrationFeaturesSupported(any())).thenReturn(true);
+ }
@Test
public void testParseCustomizations_noCustomization_success() throws Exception {
@@ -70,7 +90,7 @@ public class HapticFeedbackCustomizationTest {
}
@Test
- public void testParseCustomizations_oneCustomization_success() throws Exception {
+ public void testParseCustomizations_oneVibrationCustomization_success() throws Exception {
String xml = "<haptic-feedback-constants>"
+ "<constant id=\"10\">"
+ COMPOSITION_VIBRATION_XML
@@ -83,22 +103,115 @@ public class HapticFeedbackCustomizationTest {
}
@Test
+ public void testParseCustomizations_oneVibrationSelectCustomization_success() throws Exception {
+ String xml = "<haptic-feedback-constants>"
+ + "<constant id=\"10\">"
+ + "<vibration-select>"
+ + COMPOSITION_VIBRATION_XML
+ + "</vibration-select>"
+ + "</constant>"
+ + "</haptic-feedback-constants>";
+ SparseArray<VibrationEffect> expectedMapping = new SparseArray<>();
+ expectedMapping.put(10, COMPOSITION_VIBRATION);
+
+ assertParseCustomizationsSucceeds(xml, expectedMapping);
+ }
+
+ @Test
public void testParseCustomizations_multipleCustomizations_success() throws Exception {
String xml = "<haptic-feedback-constants>"
+ "<constant id=\"1\">"
+ COMPOSITION_VIBRATION_XML
+ "</constant>"
+ "<constant id=\"12\">"
+ + "<vibration-select>"
+ PREDEFINED_VIBRATION_XML
+ + WAVEFORM_VIBRATION_XML
+ + "</vibration-select>"
+ "</constant>"
+ "<constant id=\"150\">"
+ PREDEFINED_VIBRATION_XML
+ "</constant>"
+ + "<constant id=\"10\">"
+ + "<vibration-select>"
+ + WAVEFORM_VIBRATION_XML
+ + COMPOSITION_VIBRATION_XML
+ + "</vibration-select>"
+ + "</constant>"
+ "</haptic-feedback-constants>";
SparseArray<VibrationEffect> expectedMapping = new SparseArray<>();
expectedMapping.put(1, COMPOSITION_VIBRATION);
expectedMapping.put(12, PREDEFINED_VIBRATION);
expectedMapping.put(150, PREDEFINED_VIBRATION);
+ expectedMapping.put(10, WAVEFORM_VIBARTION);
+
+ assertParseCustomizationsSucceeds(xml, expectedMapping);
+ }
+
+ @Test
+ public void testParseCustomizations_multipleCustomizations_noSupportedVibration_success()
+ throws Exception {
+ makeUnsupported(COMPOSITION_VIBRATION, PREDEFINED_VIBRATION, WAVEFORM_VIBARTION);
+ String xml = "<haptic-feedback-constants>"
+ + "<constant id=\"1\">"
+ + COMPOSITION_VIBRATION_XML
+ + "</constant>"
+ + "<constant id=\"12\">"
+ + "<vibration-select>"
+ + PREDEFINED_VIBRATION_XML
+ + WAVEFORM_VIBRATION_XML
+ + "</vibration-select>"
+ + "</constant>"
+ + "<constant id=\"150\">"
+ + PREDEFINED_VIBRATION_XML
+ + "</constant>"
+ + "<constant id=\"10\">"
+ + "<vibration-select>"
+ + WAVEFORM_VIBRATION_XML
+ + COMPOSITION_VIBRATION_XML
+ + "</vibration-select>"
+ + "</constant>"
+ + "</haptic-feedback-constants>";
+
+ assertParseCustomizationsSucceeds(xml, new SparseArray<>());
+ }
+
+ @Test
+ public void testParseCustomizations_multipleCustomizations_someUnsupportedVibration_success()
+ throws Exception {
+ makeSupported(PREDEFINED_VIBRATION, WAVEFORM_VIBARTION);
+ makeUnsupported(COMPOSITION_VIBRATION);
+ String xml = "<haptic-feedback-constants>"
+ + "<constant id=\"1\">" // No supported customization.
+ + COMPOSITION_VIBRATION_XML
+ + "</constant>"
+ + "<constant id=\"12\">" // PREDEFINED_VIBRATION is the first/only supported.
+ + "<vibration-select>"
+ + PREDEFINED_VIBRATION_XML
+ + COMPOSITION_VIBRATION_XML
+ + "</vibration-select>"
+ + "</constant>"
+ + "<constant id=\"14\">" // WAVEFORM_VIBARTION is the first/only supported.
+ + "<vibration-select>"
+ + COMPOSITION_VIBRATION_XML
+ + WAVEFORM_VIBRATION_XML
+ + "</vibration-select>"
+ + "</constant>"
+ + "<constant id=\"150\">" // PREDEFINED_VIBRATION is the first/only supported.
+ + PREDEFINED_VIBRATION_XML
+ + "</constant>"
+ + "<constant id=\"10\">" // PREDEFINED_VIBRATION is the first supported.
+ + "<vibration-select>"
+ + PREDEFINED_VIBRATION_XML
+ + WAVEFORM_VIBRATION_XML
+ + "</vibration-select>"
+ + "</constant>"
+ + "</haptic-feedback-constants>";
+ SparseArray<VibrationEffect> expectedMapping = new SparseArray<>();
+ expectedMapping.put(12, PREDEFINED_VIBRATION);
+ expectedMapping.put(14, WAVEFORM_VIBARTION);
+ expectedMapping.put(150, PREDEFINED_VIBRATION);
+ expectedMapping.put(10, PREDEFINED_VIBRATION);
assertParseCustomizationsSucceeds(xml, expectedMapping);
}
@@ -107,15 +220,18 @@ public class HapticFeedbackCustomizationTest {
public void testParseCustomizations_noCustomizationFile_returnsNull() throws Exception {
setCustomizationFilePath("");
- assertThat(HapticFeedbackCustomization.loadVibrations(mResourcesMock)).isNull();
+ assertThat(HapticFeedbackCustomization.loadVibrations(mResourcesMock, mVibratorMock))
+ .isNull();
setCustomizationFilePath(null);
- assertThat(HapticFeedbackCustomization.loadVibrations(mResourcesMock)).isNull();
+ assertThat(HapticFeedbackCustomization.loadVibrations(mResourcesMock, mVibratorMock))
+ .isNull();
setCustomizationFilePath("non_existent_file.xml");
- assertThat(HapticFeedbackCustomization.loadVibrations(mResourcesMock)).isNull();
+ assertThat(HapticFeedbackCustomization.loadVibrations(mResourcesMock, mVibratorMock))
+ .isNull();
}
@Test
@@ -216,6 +332,22 @@ public class HapticFeedbackCustomizationTest {
+ "<vibration><predefined-effect name=\"bad-effect-name\"/></vibration>"
+ "</constant>"
+ "</haptic-feedback-constants>");
+
+ assertParseCustomizationsFails(
+ "<haptic-feedback-constants>"
+ + "<constant id=\"10\">"
+ + "<vibration-select>"
+ + "<vibration><predefined-effect name=\"bad-effect-name\"/></vibration>"
+ + "</constant>"
+ + "</haptic-feedback-constants>");
+
+ assertParseCustomizationsFails(
+ "<haptic-feedback-constants>"
+ + "<constant id=\"10\">"
+ + "<vibration><predefined-effect name=\"bad-effect-name\"/></vibration>"
+ + "</vibration-select>"
+ + "</constant>"
+ + "</haptic-feedback-constants>");
}
@Test
@@ -255,20 +387,21 @@ public class HapticFeedbackCustomizationTest {
String xml, SparseArray<VibrationEffect> expectedCustomizations) throws Exception {
setupCustomizationFile(xml);
assertThat(expectedCustomizations.contentEquals(
- HapticFeedbackCustomization.loadVibrations(mResourcesMock))).isTrue();
+ HapticFeedbackCustomization.loadVibrations(mResourcesMock, mVibratorMock)))
+ .isTrue();
}
private void assertParseCustomizationsFails(String xml) throws Exception {
setupCustomizationFile(xml);
assertThrows("Expected haptic feedback customization to fail for " + xml,
CustomizationParserException.class,
- () -> HapticFeedbackCustomization.loadVibrations(mResourcesMock));
+ () -> HapticFeedbackCustomization.loadVibrations(mResourcesMock, mVibratorMock));
}
private void assertParseCustomizationsFails() throws Exception {
assertThrows("Expected haptic feedback customization to fail",
CustomizationParserException.class,
- () -> HapticFeedbackCustomization.loadVibrations(mResourcesMock));
+ () -> HapticFeedbackCustomization.loadVibrations(mResourcesMock, mVibratorMock));
}
private void setupCustomizationFile(String xml) throws Exception {
@@ -281,6 +414,18 @@ public class HapticFeedbackCustomizationTest {
.thenReturn(path);
}
+ private void makeSupported(VibrationEffect... effects) {
+ for (VibrationEffect effect : effects) {
+ when(mVibratorInfoMock.areVibrationFeaturesSupported(effect)).thenReturn(true);
+ }
+ }
+
+ private void makeUnsupported(VibrationEffect... effects) {
+ for (VibrationEffect effect : effects) {
+ when(mVibratorInfoMock.areVibrationFeaturesSupported(effect)).thenReturn(false);
+ }
+ }
+
private static File createFile(String contents) throws Exception {
File file = new File(InstrumentationRegistry.getContext().getCacheDir(), "test.xml");
file.createNewFile();