summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/java/android/os/vibrator/flags.aconfig11
-rw-r--r--core/res/res/values/symbols.xml2
-rw-r--r--core/res/res/xml/haptic_feedback_customization.xml18
-rw-r--r--services/core/java/com/android/server/vibrator/HapticFeedbackCustomization.java71
-rw-r--r--services/tests/vibrator/Android.bp1
-rw-r--r--services/tests/vibrator/src/com/android/server/vibrator/HapticFeedbackCustomizationTest.java262
-rw-r--r--services/tests/vibrator/utils/com/android/server/vibrator/FakeXmlResourceParser.java330
7 files changed, 584 insertions, 111 deletions
diff --git a/core/java/android/os/vibrator/flags.aconfig b/core/java/android/os/vibrator/flags.aconfig
index 3fad8dd1da9c..0ed9b0316e4a 100644
--- a/core/java/android/os/vibrator/flags.aconfig
+++ b/core/java/android/os/vibrator/flags.aconfig
@@ -88,3 +88,14 @@ flag {
purpose: PURPOSE_BUGFIX
}
}
+
+flag {
+ namespace: "haptics"
+ name: "load_haptic_feedback_vibration_customization_from_resources"
+ description: "Load haptic feedback vibrations customization from resources."
+ is_fixed_read_only: true
+ bug: "295142743"
+ metadata {
+ purpose: PURPOSE_FEATURE
+ }
+}
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index bdcba9daa5ad..0d16e9c939d9 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -5462,7 +5462,9 @@
<java-symbol type="bool" name="config_enable_a11y_fullscreen_magnification_overscroll_handler" />
<java-symbol type="dimen" name="accessibility_fullscreen_magnification_gesture_edge_slop" />
+ <!-- For HapticFeedbackConstants configurability defined at HapticFeedbackCustomization -->
<java-symbol type="string" name="config_hapticFeedbackCustomizationFile" />
+ <java-symbol type="xml" name="haptic_feedback_customization" />
<!-- For ActivityManager PSS profiling configurability -->
<java-symbol type="bool" name="config_am_disablePssProfiling" />
diff --git a/core/res/res/xml/haptic_feedback_customization.xml b/core/res/res/xml/haptic_feedback_customization.xml
new file mode 100644
index 000000000000..7ac0787ab7a0
--- /dev/null
+++ b/core/res/res/xml/haptic_feedback_customization.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2024 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.
+ -->
+
+<haptic-feedback-constants/>
diff --git a/services/core/java/com/android/server/vibrator/HapticFeedbackCustomization.java b/services/core/java/com/android/server/vibrator/HapticFeedbackCustomization.java
index 503a7268d5d3..65fc7b2c5c39 100644
--- a/services/core/java/com/android/server/vibrator/HapticFeedbackCustomization.java
+++ b/services/core/java/com/android/server/vibrator/HapticFeedbackCustomization.java
@@ -18,6 +18,7 @@ package com.android.server.vibrator;
import android.annotation.Nullable;
import android.content.res.Resources;
+import android.content.res.XmlResourceParser;
import android.os.VibrationEffect;
import android.os.VibratorInfo;
import android.os.vibrator.Flags;
@@ -28,6 +29,7 @@ import android.util.Slog;
import android.util.SparseArray;
import android.util.Xml;
+import com.android.internal.util.XmlUtils;
import com.android.internal.vibrator.persistence.XmlParserException;
import com.android.internal.vibrator.persistence.XmlReader;
import com.android.internal.vibrator.persistence.XmlValidator;
@@ -39,6 +41,7 @@ import org.xmlpull.v1.XmlPullParserException;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
+import java.io.Reader;
/**
* Class that loads custom {@link VibrationEffect} to be performed for each
@@ -127,27 +130,19 @@ final class HapticFeedbackCustomization {
Slog.d(TAG, "Haptic feedback customization feature is not enabled.");
return null;
}
- String customizationFile =
- res.getString(
- com.android.internal.R.string.config_hapticFeedbackCustomizationFile);
- if (TextUtils.isEmpty(customizationFile)) {
- Slog.d(TAG, "Customization file not configured.");
- return null;
- }
- FileReader fileReader;
- try {
- fileReader = new FileReader(customizationFile);
- } catch (FileNotFoundException e) {
- Slog.d(TAG, "Specified customization file not found.");
- return null;
+ // Old loading path that reads customization from file at dir defined by config.
+ TypedXmlPullParser parser = readCustomizationFile(res);
+ if (parser == null) {
+ // When old loading path doesn't succeed, try loading customization from resources.
+ parser = readCustomizationResources(res);
+ }
+ if (parser == null) {
+ Slog.d(TAG, "No loadable haptic feedback customization.");
+ return null;
}
- TypedXmlPullParser parser = Xml.newFastPullParser();
- parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true);
- parser.setInput(fileReader);
-
- XmlReader.readDocumentStartTag(parser, TAG_CONSTANTS);
+ XmlUtils.beginDocument(parser, TAG_CONSTANTS);
XmlValidator.checkTagHasNoUnexpectedAttributes(parser);
int rootDepth = parser.getDepth();
@@ -191,6 +186,46 @@ final class HapticFeedbackCustomization {
return mapping;
}
+ // TODO(b/356412421): deprecate old path related files.
+ private static TypedXmlPullParser readCustomizationFile(Resources res)
+ throws XmlPullParserException {
+ String customizationFile = res.getString(
+ com.android.internal.R.string.config_hapticFeedbackCustomizationFile);
+ if (TextUtils.isEmpty(customizationFile)) {
+ return null;
+ }
+
+ final Reader customizationReader;
+ try {
+ customizationReader = new FileReader(customizationFile);
+ } catch (FileNotFoundException e) {
+ Slog.e(TAG, "Specified customization file not found.", e);
+ return null;
+ }
+
+ final TypedXmlPullParser parser;
+ parser = Xml.newFastPullParser();
+ parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true);
+ parser.setInput(customizationReader);
+ Slog.d(TAG, "Successfully opened customization file.");
+ return parser;
+ }
+
+ private static TypedXmlPullParser readCustomizationResources(Resources res) {
+ if (!Flags.loadHapticFeedbackVibrationCustomizationFromResources()) {
+ return null;
+ }
+ final XmlResourceParser resParser;
+ try {
+ resParser = res.getXml(com.android.internal.R.xml.haptic_feedback_customization);
+ } catch (Resources.NotFoundException e) {
+ Slog.e(TAG, "Haptic customization resource not found.", e);
+ return null;
+ }
+ Slog.d(TAG, "Successfully opened customization resource.");
+ return XmlUtils.makeTyped(resParser);
+ }
+
/**
* Represents an error while parsing a haptic feedback customization XML.
*/
diff --git a/services/tests/vibrator/Android.bp b/services/tests/vibrator/Android.bp
index 757bcd8e2193..43ad44f057cc 100644
--- a/services/tests/vibrator/Android.bp
+++ b/services/tests/vibrator/Android.bp
@@ -32,6 +32,7 @@ android_test {
"frameworks-base-testutils",
"frameworks-services-vibrator-testutils",
"junit",
+ "junit-params",
"mockito-target-inline-minus-junit4",
"platform-test-annotations",
"service-permission.stubs.system_server",
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 2b23b1897f59..e0d05df1de80 100644
--- a/services/tests/vibrator/src/com/android/server/vibrator/HapticFeedbackCustomizationTest.java
+++ b/services/tests/vibrator/src/com/android/server/vibrator/HapticFeedbackCustomizationTest.java
@@ -16,16 +16,17 @@
package com.android.server.vibrator;
-
import static android.os.VibrationEffect.Composition.PRIMITIVE_TICK;
import static android.os.VibrationEffect.EFFECT_CLICK;
+import static com.android.internal.R.xml.haptic_feedback_customization;
import static com.android.server.vibrator.HapticFeedbackCustomization.CustomizationParserException;
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.doThrow;
import static org.mockito.Mockito.when;
import android.content.res.Resources;
@@ -39,10 +40,15 @@ import android.util.SparseArray;
import androidx.test.InstrumentationRegistry;
import com.android.internal.R;
+import com.android.internal.annotations.Keep;
+
+import junitparams.JUnitParamsRunner;
+import junitparams.Parameters;
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.MockitoRule;
@@ -50,6 +56,7 @@ import org.mockito.junit.MockitoRule;
import java.io.File;
import java.io.FileOutputStream;
+@RunWith(JUnitParamsRunner.class)
public class HapticFeedbackCustomizationTest {
@Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
@@ -78,21 +85,35 @@ public class HapticFeedbackCustomizationTest {
@Mock private Resources mResourcesMock;
@Mock private VibratorInfo mVibratorInfoMock;
+ @Keep
+ private static Object[][] hapticFeedbackCustomizationTestArguments() {
+ // (boolean hasConfigFile, boolean hasRes).
+ return new Object[][] {{true, true}, {true, false}, {false, true}};
+ }
+
@Before
public void setUp() {
when(mVibratorInfoMock.areVibrationFeaturesSupported(any())).thenReturn(true);
mSetFlagsRule.enableFlags(Flags.FLAG_HAPTIC_FEEDBACK_VIBRATION_OEM_CUSTOMIZATION_ENABLED);
+ mSetFlagsRule.disableFlags(
+ Flags.FLAG_LOAD_HAPTIC_FEEDBACK_VIBRATION_CUSTOMIZATION_FROM_RESOURCES);
}
@Test
- public void testParseCustomizations_noCustomization_success() throws Exception {
- assertParseCustomizationsSucceeds(
- /* xml= */ "<haptic-feedback-constants></haptic-feedback-constants>",
- /* expectedCustomizations= */ new SparseArray<>());
+ @Parameters(method = "hapticFeedbackCustomizationTestArguments")
+ public void testParseCustomizations_noCustomization_success(
+ boolean hasConfigFile, boolean hasRes) throws Exception {
+ String xml = "<haptic-feedback-constants></haptic-feedback-constants>";
+ SparseArray<VibrationEffect> expectedMapping = new SparseArray<>();
+ setupParseCustomizations(xml, hasConfigFile, hasRes);
+
+ assertParseCustomizationsSucceeds(xml, expectedMapping, hasConfigFile, hasRes);
}
@Test
- public void testParseCustomizations_featureFlagDisabled_returnsNull() throws Exception {
+ @Parameters(method = "hapticFeedbackCustomizationTestArguments")
+ public void testParseCustomizations_featureFlagDisabled_returnsNull(
+ boolean hasConfigFile, boolean hasRes) throws Exception {
mSetFlagsRule.disableFlags(Flags.FLAG_HAPTIC_FEEDBACK_VIBRATION_OEM_CUSTOMIZATION_ENABLED);
// Valid customization XML.
String xml = "<haptic-feedback-constants>"
@@ -100,14 +121,16 @@ public class HapticFeedbackCustomizationTest {
+ COMPOSITION_VIBRATION_XML
+ "</constant>"
+ "</haptic-feedback-constants>";
- setupCustomizationFile(xml);
+ setupParseCustomizations(xml, hasConfigFile, hasRes);
assertThat(HapticFeedbackCustomization.loadVibrations(mResourcesMock, mVibratorInfoMock))
.isNull();
}
@Test
- public void testParseCustomizations_oneVibrationCustomization_success() throws Exception {
+ @Parameters(method = "hapticFeedbackCustomizationTestArguments")
+ public void testParseCustomizations_oneVibrationCustomization_success(
+ boolean hasConfigFile, boolean hasRes) throws Exception {
String xml = "<haptic-feedback-constants>"
+ "<constant id=\"10\">"
+ COMPOSITION_VIBRATION_XML
@@ -116,11 +139,13 @@ public class HapticFeedbackCustomizationTest {
SparseArray<VibrationEffect> expectedMapping = new SparseArray<>();
expectedMapping.put(10, COMPOSITION_VIBRATION);
- assertParseCustomizationsSucceeds(xml, expectedMapping);
+ assertParseCustomizationsSucceeds(xml, expectedMapping, hasConfigFile, hasRes);
}
@Test
- public void testParseCustomizations_oneVibrationSelectCustomization_success() throws Exception {
+ @Parameters(method = "hapticFeedbackCustomizationTestArguments")
+ public void testParseCustomizations_oneVibrationSelectCustomization_success(
+ boolean hasConfigFile, boolean hasRes) throws Exception {
String xml = "<haptic-feedback-constants>"
+ "<constant id=\"10\">"
+ "<vibration-select>"
@@ -131,11 +156,13 @@ public class HapticFeedbackCustomizationTest {
SparseArray<VibrationEffect> expectedMapping = new SparseArray<>();
expectedMapping.put(10, COMPOSITION_VIBRATION);
- assertParseCustomizationsSucceeds(xml, expectedMapping);
+ assertParseCustomizationsSucceeds(xml, expectedMapping, hasConfigFile, hasRes);
}
@Test
- public void testParseCustomizations_multipleCustomizations_success() throws Exception {
+ @Parameters(method = "hapticFeedbackCustomizationTestArguments")
+ public void testParseCustomizations_multipleCustomizations_success(
+ boolean hasConfigFile, boolean hasRes) throws Exception {
String xml = "<haptic-feedback-constants>"
+ "<constant id=\"1\">"
+ COMPOSITION_VIBRATION_XML
@@ -162,11 +189,13 @@ public class HapticFeedbackCustomizationTest {
expectedMapping.put(150, PREDEFINED_VIBRATION);
expectedMapping.put(10, WAVEFORM_VIBARTION);
- assertParseCustomizationsSucceeds(xml, expectedMapping);
+ assertParseCustomizationsSucceeds(xml, expectedMapping, hasConfigFile, hasRes);
}
@Test
- public void testParseCustomizations_multipleCustomizations_noSupportedVibration_success()
+ @Parameters(method = "hapticFeedbackCustomizationTestArguments")
+ public void testParseCustomizations_multipleCustomizations_noSupportedVibration_success(
+ boolean hasConfigFile, boolean hasRes)
throws Exception {
makeUnsupported(COMPOSITION_VIBRATION, PREDEFINED_VIBRATION, WAVEFORM_VIBARTION);
String xml = "<haptic-feedback-constants>"
@@ -189,13 +218,16 @@ public class HapticFeedbackCustomizationTest {
+ "</vibration-select>"
+ "</constant>"
+ "</haptic-feedback-constants>";
+ SparseArray<VibrationEffect> expectedMapping = new SparseArray<>();
- assertParseCustomizationsSucceeds(xml, new SparseArray<>());
+ assertParseCustomizationsSucceeds(xml, expectedMapping, hasConfigFile, hasRes);
}
@Test
- public void testParseCustomizations_multipleCustomizations_someUnsupportedVibration_success()
- throws Exception {
+ @Parameters(method = "hapticFeedbackCustomizationTestArguments")
+ public void testParseCustomizations_multipleCustomizations_someUnsupportedVibration_success(
+ boolean hasConfigFile, boolean hasRes)
+ throws Exception {
makeSupported(PREDEFINED_VIBRATION, WAVEFORM_VIBARTION);
makeUnsupported(COMPOSITION_VIBRATION);
String xml = "<haptic-feedback-constants>"
@@ -230,7 +262,7 @@ public class HapticFeedbackCustomizationTest {
expectedMapping.put(150, PREDEFINED_VIBRATION);
expectedMapping.put(10, PREDEFINED_VIBRATION);
- assertParseCustomizationsSucceeds(xml, expectedMapping);
+ assertParseCustomizationsSucceeds(xml, expectedMapping, hasConfigFile, hasRes);
}
@Test
@@ -252,12 +284,23 @@ public class HapticFeedbackCustomizationTest {
}
@Test
- public void testParseCustomizations_disallowedVibrationForHapticFeedback_throwsException()
- throws Exception {
+ public void testParseCustomizations_noCustomizationResource_returnsNull() throws Exception {
+ mSetFlagsRule.enableFlags(
+ Flags.FLAG_LOAD_HAPTIC_FEEDBACK_VIBRATION_CUSTOMIZATION_FROM_RESOURCES);
+ doThrow(new Resources.NotFoundException())
+ .when(mResourcesMock).getXml(haptic_feedback_customization);
+
+ assertThat(HapticFeedbackCustomization.loadVibrations(mResourcesMock, mVibratorInfoMock))
+ .isNull();
+ }
+
+ @Test
+ @Parameters(method = "hapticFeedbackCustomizationTestArguments")
+ public void testParseCustomizations_disallowedVibrationForHapticFeedback_throwsException(
+ boolean hasConfigFile, boolean hasRes) throws Exception {
// The XML content is good, but the serialized vibration is not supported for haptic
// feedback usage (i.e. repeating vibration).
- assertParseCustomizationsFails(
- "<haptic-feedback-constants>"
+ String xml = "<haptic-feedback-constants>"
+ "<constant id=\"10\">"
+ "<vibration-effect>"
+ "<waveform-effect>"
@@ -267,127 +310,139 @@ public class HapticFeedbackCustomizationTest {
+ "</waveform-effect>"
+ "</vibration-effect>"
+ "</constant>"
- + "</haptic-feedback-constants>");
+ + "</haptic-feedback-constants>";
+
+ assertParseCustomizationsFails(xml, hasConfigFile, hasRes);
}
@Test
- public void testParseCustomizations_emptyXml_throwsException() throws Exception {
- assertParseCustomizationsFails("");
+ @Parameters(method = "hapticFeedbackCustomizationTestArguments")
+ public void testParseCustomizations_emptyXml_throwsException(
+ boolean hasConfigFile, boolean hasRes) throws Exception {
+ assertParseCustomizationsFails("", hasConfigFile, hasRes);
}
@Test
- public void testParseCustomizations_noVibrationXml_throwsException() throws Exception {
- assertParseCustomizationsFails(
- "<haptic-feedback-constants>"
+ @Parameters(method = "hapticFeedbackCustomizationTestArguments")
+ public void testParseCustomizations_noVibrationXml_throwsException(
+ boolean hasConfigFile, boolean hasRes) throws Exception {
+ String xml = "<haptic-feedback-constants>"
+ "<constant id=\"1\">"
+ "</constant>"
- + "</haptic-feedback-constants>");
+ + "</haptic-feedback-constants>";
+
+ assertParseCustomizationsFails(xml, hasConfigFile, hasRes);
}
@Test
- public void testParseCustomizations_badEffectId_throwsException() throws Exception {
+ @Parameters(method = "hapticFeedbackCustomizationTestArguments")
+ public void testParseCustomizations_badEffectId_throwsException(
+ boolean hasConfigFile, boolean hasRes) throws Exception {
// Negative id
- assertParseCustomizationsFails(
- "<haptic-feedback-constants>"
+ String xmlNegativeId = "<haptic-feedback-constants>"
+ "<constant id=\"-10\">"
+ COMPOSITION_VIBRATION_XML
+ "</constant>"
- + "</haptic-feedback-constants>");
-
+ + "</haptic-feedback-constants>";
// Non-numeral id
- assertParseCustomizationsFails(
- "<haptic-feedback-constants>"
+ String xmlNonNumericalId = "<haptic-feedback-constants>"
+ "<constant id=\"xyz\">"
+ COMPOSITION_VIBRATION_XML
+ "</constant>"
- + "</haptic-feedback-constants>");
+ + "</haptic-feedback-constants>";
+
+ assertParseCustomizationsFails(xmlNegativeId, hasConfigFile, hasRes);
+ assertParseCustomizationsFails(xmlNonNumericalId, hasConfigFile, hasRes);
}
@Test
- public void testParseCustomizations_malformedXml_throwsException() throws Exception {
+ @Parameters(method = "hapticFeedbackCustomizationTestArguments")
+ public void testParseCustomizations_malformedXml_throwsException(
+ boolean hasConfigFile, boolean hasRes) throws Exception {
// No start "<constant>" tag
- assertParseCustomizationsFails(
- "<haptic-feedback-constants>"
+ String xmlNoStartConstantTag = "<haptic-feedback-constants>"
+ COMPOSITION_VIBRATION_XML
+ "</constant>"
- + "</haptic-feedback-constants>");
-
+ + "</haptic-feedback-constants>";
// No end "<constant>" tag
- assertParseCustomizationsFails(
- "<haptic-feedback-constants>"
+ String xmlNoEndConstantTag = "<haptic-feedback-constants>"
+ "<constant id=\"10\">"
+ COMPOSITION_VIBRATION_XML
- + "</haptic-feedback-constants>");
-
+ + "</haptic-feedback-constants>";
// No start "<haptic-feedback-constants>" tag
- assertParseCustomizationsFails(
- "<constant id=\"10\">"
+ String xmlNoStartCustomizationTag = "<constant id=\"10\">"
+ COMPOSITION_VIBRATION_XML
+ "</constant>"
- + "</haptic-feedback-constants>");
-
+ + "</haptic-feedback-constants>";
// No end "<haptic-feedback-constants>" tag
- assertParseCustomizationsFails(
- "<haptic-feedback-constants>"
+ String xmlNoEndCustomizationTag = "<haptic-feedback-constants>"
+ "<constant id=\"10\">"
+ COMPOSITION_VIBRATION_XML
- + "</constant>");
+ + "</constant>";
+
+ assertParseCustomizationsFails(xmlNoStartConstantTag, hasConfigFile, hasRes);
+ assertParseCustomizationsFails(xmlNoEndConstantTag, hasConfigFile, hasRes);
+ assertParseCustomizationsFails(xmlNoStartCustomizationTag, hasConfigFile, hasRes);
+ assertParseCustomizationsFails(xmlNoEndCustomizationTag, hasConfigFile, hasRes);
}
@Test
- public void testParseCustomizations_badVibrationXml_throwsException() throws Exception {
- assertParseCustomizationsFails(
- "<haptic-feedback-constants>"
+ @Parameters(method = "hapticFeedbackCustomizationTestArguments")
+ public void testParseCustomizations_badVibrationXml_throwsException(
+ boolean hasConfigFile, boolean hasRes) throws Exception {
+ String xmlBad1 = "<haptic-feedback-constants>"
+ "<constant id=\"10\">"
+ "<bad-vibration-effect></bad-vibration-effect>"
+ "</constant>"
- + "</haptic-feedback-constants>");
-
- assertParseCustomizationsFails(
- "<haptic-feedback-constants>"
+ + "</haptic-feedback-constants>";
+ String xmlBad2 = "<haptic-feedback-constants>"
+ "<constant id=\"10\">"
+ "<vibration-effect><predefined-effect name=\"bad-effect\"/></vibration-effect>"
+ "</constant>"
- + "</haptic-feedback-constants>");
-
- assertParseCustomizationsFails(
- "<haptic-feedback-constants>"
+ + "</haptic-feedback-constants>";
+ String xmlBad3 = "<haptic-feedback-constants>"
+ "<constant id=\"10\">"
+ "<vibration-select>"
+ "<vibration-effect><predefined-effect name=\"bad-effect\"/></vibration-effect>"
+ "</constant>"
- + "</haptic-feedback-constants>");
-
- assertParseCustomizationsFails(
- "<haptic-feedback-constants>"
+ + "</haptic-feedback-constants>";
+ String xmlBad4 = "<haptic-feedback-constants>"
+ "<constant id=\"10\">"
+ "<vibration-effect><predefined-effect name=\"bad-effect\"/></vibration-effect>"
+ "</vibration-select>"
+ "</constant>"
- + "</haptic-feedback-constants>");
+ + "</haptic-feedback-constants>";
+
+ assertParseCustomizationsFails(xmlBad1, hasConfigFile, hasRes);
+ assertParseCustomizationsFails(xmlBad2, hasConfigFile, hasRes);
+ assertParseCustomizationsFails(xmlBad3, hasConfigFile, hasRes);
+ assertParseCustomizationsFails(xmlBad4, hasConfigFile, hasRes);
}
@Test
- public void testParseCustomizations_badConstantAttribute_throwsException() throws Exception {
- assertParseCustomizationsFails(
- "<haptic-feedback-constants>"
+ @Parameters(method = "hapticFeedbackCustomizationTestArguments")
+ public void testParseCustomizations_badConstantAttribute_throwsException(
+ boolean hasConfigFile, boolean hasRes) throws Exception {
+ String xmlBadConstantAttribute1 = "<haptic-feedback-constants>"
+ "<constant iddddd=\"10\">"
+ COMPOSITION_VIBRATION_XML
+ "</constant>"
- + "</haptic-feedback-constants>");
-
- assertParseCustomizationsFails(
- "<haptic-feedback-constants>"
+ + "</haptic-feedback-constants>";
+ String xmlBadConstantAttribute2 = "<haptic-feedback-constants>"
+ "<constant id=\"10\" unwanted-attr=\"1\">"
+ COMPOSITION_VIBRATION_XML
+ "</constant>"
- + "</haptic-feedback-constants>");
+ + "</haptic-feedback-constants>";
+
+ assertParseCustomizationsFails(xmlBadConstantAttribute1, hasConfigFile, hasRes);
+ assertParseCustomizationsFails(xmlBadConstantAttribute2, hasConfigFile, hasRes);
}
@Test
- public void testParseCustomizations_duplicateEffects_throwsException() throws Exception {
- assertParseCustomizationsFails(
- "<haptic-feedback-constants>"
+ @Parameters(method = "hapticFeedbackCustomizationTestArguments")
+ public void testParseCustomizations_duplicateEffects_throwsException(
+ boolean hasConfigFile, boolean hasRes) throws Exception {
+ String xmlDuplicateEffect = "<haptic-feedback-constants>"
+ "<constant id=\"10\">"
+ COMPOSITION_VIBRATION_XML
+ "</constant>"
@@ -397,30 +452,44 @@ public class HapticFeedbackCustomizationTest {
+ "<constant id=\"11\">"
+ PREDEFINED_VIBRATION_XML
+ "</constant>"
- + "</haptic-feedback-constants>");
+ + "</haptic-feedback-constants>";
+
+ assertParseCustomizationsFails(xmlDuplicateEffect, hasConfigFile, hasRes);
}
- private void assertParseCustomizationsSucceeds(
- String xml, SparseArray<VibrationEffect> expectedCustomizations) throws Exception {
- setupCustomizationFile(xml);
+ private void assertParseCustomizationsSucceeds(String xml,
+ SparseArray<VibrationEffect> expectedCustomizations, boolean hasConfigFile,
+ boolean hasRes) throws Exception {
+ setupParseCustomizations(xml, hasConfigFile, hasRes);
assertThat(expectedCustomizations.contentEquals(
HapticFeedbackCustomization.loadVibrations(mResourcesMock, mVibratorInfoMock)))
- .isTrue();
+ .isTrue();
}
- private void assertParseCustomizationsFails(String xml) throws Exception {
- setupCustomizationFile(xml);
- assertThrows("Expected haptic feedback customization to fail for " + xml,
+ private void assertParseCustomizationsFails(String xml, boolean hasConfigFile, boolean hasRes)
+ throws Exception {
+ setupParseCustomizations(xml, hasConfigFile, hasRes);
+ assertThrows("Expected haptic feedback customization to fail",
CustomizationParserException.class,
() -> HapticFeedbackCustomization.loadVibrations(
mResourcesMock, mVibratorInfoMock));
}
- private void assertParseCustomizationsFails() throws Exception {
- assertThrows("Expected haptic feedback customization to fail",
- CustomizationParserException.class,
- () -> HapticFeedbackCustomization.loadVibrations(
- mResourcesMock, mVibratorInfoMock));
+ private void setupParseCustomizations(String xml, boolean hasConfigFile, boolean hasRes)
+ throws Exception {
+ clearFileAndResourceSetup();
+ if (hasConfigFile) {
+ setupCustomizationFile(xml);
+ }
+ if (hasRes) {
+ setupCustomizationResource(xml);
+ }
+ }
+
+ private void clearFileAndResourceSetup() {
+ when(mResourcesMock.getString(R.string.config_hapticFeedbackCustomizationFile))
+ .thenReturn(null);
+ when(mResourcesMock.getXml(haptic_feedback_customization)).thenReturn(null);
}
private void setupCustomizationFile(String xml) throws Exception {
@@ -433,6 +502,13 @@ public class HapticFeedbackCustomizationTest {
.thenReturn(path);
}
+ private void setupCustomizationResource(String xml) throws Exception {
+ mSetFlagsRule.enableFlags(
+ Flags.FLAG_LOAD_HAPTIC_FEEDBACK_VIBRATION_CUSTOMIZATION_FROM_RESOURCES);
+ when(mResourcesMock.getXml(haptic_feedback_customization))
+ .thenReturn(FakeXmlResourceParser.fromXml(xml));
+ }
+
private void makeSupported(VibrationEffect... effects) {
for (VibrationEffect effect : effects) {
when(mVibratorInfoMock.areVibrationFeaturesSupported(effect)).thenReturn(true);
diff --git a/services/tests/vibrator/utils/com/android/server/vibrator/FakeXmlResourceParser.java b/services/tests/vibrator/utils/com/android/server/vibrator/FakeXmlResourceParser.java
new file mode 100644
index 000000000000..ab7d43c66765
--- /dev/null
+++ b/services/tests/vibrator/utils/com/android/server/vibrator/FakeXmlResourceParser.java
@@ -0,0 +1,330 @@
+/*
+ * Copyright (C) 2024 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 com.android.server.vibrator;
+
+import android.content.res.XmlResourceParser;
+import android.util.Xml;
+
+import com.android.modules.utils.TypedXmlPullParser;
+
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.BufferedInputStream;
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.Reader;
+
+/**
+ * Wrapper to use TypedXmlPullParser as XmlResourceParser for Resources.getXml(). This is borrowed
+ * from {@code ZenModeHelperTest}.
+ */
+public final class FakeXmlResourceParser implements XmlResourceParser {
+ private final TypedXmlPullParser mParser;
+
+ public FakeXmlResourceParser(TypedXmlPullParser parser) {
+ this.mParser = parser;
+ }
+
+ /** Create a {@link FakeXmlResourceParser} given a xml {@link String}. */
+ public static XmlResourceParser fromXml(String xml) throws XmlPullParserException {
+ TypedXmlPullParser parser = Xml.newFastPullParser();
+ parser.setInput(new BufferedInputStream(new ByteArrayInputStream(xml.getBytes())), null);
+ return new FakeXmlResourceParser(parser);
+ }
+
+ @Override
+ public int getEventType() throws XmlPullParserException {
+ return mParser.getEventType();
+ }
+
+ @Override
+ public void setFeature(String name, boolean state) throws XmlPullParserException {
+ mParser.setFeature(name, state);
+ }
+
+ @Override
+ public boolean getFeature(String name) {
+ return false;
+ }
+
+ @Override
+ public void setProperty(String name, Object value) throws XmlPullParserException {
+ mParser.setProperty(name, value);
+ }
+
+ @Override
+ public Object getProperty(String name) {
+ return mParser.getProperty(name);
+ }
+
+ @Override
+ public void setInput(Reader in) throws XmlPullParserException {
+ mParser.setInput(in);
+ }
+
+ @Override
+ public void setInput(InputStream inputStream, String inputEncoding)
+ throws XmlPullParserException {
+ mParser.setInput(inputStream, inputEncoding);
+ }
+
+ @Override
+ public String getInputEncoding() {
+ return mParser.getInputEncoding();
+ }
+
+ @Override
+ public void defineEntityReplacementText(String entityName, String replacementText)
+ throws XmlPullParserException {
+ mParser.defineEntityReplacementText(entityName, replacementText);
+ }
+
+ @Override
+ public int getNamespaceCount(int depth) throws XmlPullParserException {
+ return mParser.getNamespaceCount(depth);
+ }
+
+ @Override
+ public String getNamespacePrefix(int pos) throws XmlPullParserException {
+ return mParser.getNamespacePrefix(pos);
+ }
+
+ @Override
+ public String getNamespaceUri(int pos) throws XmlPullParserException {
+ return mParser.getNamespaceUri(pos);
+ }
+
+ @Override
+ public String getNamespace(String prefix) {
+ return mParser.getNamespace(prefix);
+ }
+
+ @Override
+ public int getDepth() {
+ return mParser.getDepth();
+ }
+
+ @Override
+ public String getPositionDescription() {
+ return mParser.getPositionDescription();
+ }
+
+ @Override
+ public int getLineNumber() {
+ return mParser.getLineNumber();
+ }
+
+ @Override
+ public int getColumnNumber() {
+ return mParser.getColumnNumber();
+ }
+
+ @Override
+ public boolean isWhitespace() throws XmlPullParserException {
+ return mParser.isWhitespace();
+ }
+
+ @Override
+ public String getText() {
+ return mParser.getText();
+ }
+
+ @Override
+ public char[] getTextCharacters(int[] holderForStartAndLength) {
+ return mParser.getTextCharacters(holderForStartAndLength);
+ }
+
+ @Override
+ public String getNamespace() {
+ return mParser.getNamespace();
+ }
+
+ @Override
+ public String getName() {
+ return mParser.getName();
+ }
+
+ @Override
+ public String getPrefix() {
+ return mParser.getPrefix();
+ }
+
+ @Override
+ public boolean isEmptyElementTag() throws XmlPullParserException {
+ return false;
+ }
+
+ @Override
+ public int getAttributeCount() {
+ return mParser.getAttributeCount();
+ }
+
+ @Override
+ public int next() throws IOException, XmlPullParserException {
+ return mParser.next();
+ }
+
+ @Override
+ public int nextToken() throws XmlPullParserException, IOException {
+ return mParser.next();
+ }
+
+ @Override
+ public void require(int type, String namespace, String name)
+ throws XmlPullParserException, IOException {
+ mParser.require(type, namespace, name);
+ }
+
+ @Override
+ public String nextText() throws XmlPullParserException, IOException {
+ return mParser.nextText();
+ }
+
+ @Override
+ public String getAttributeNamespace(int index) {
+ return "";
+ }
+
+ @Override
+ public String getAttributeName(int index) {
+ return mParser.getAttributeName(index);
+ }
+
+ @Override
+ public String getAttributePrefix(int index) {
+ return mParser.getAttributePrefix(index);
+ }
+
+ @Override
+ public String getAttributeType(int index) {
+ return mParser.getAttributeType(index);
+ }
+
+ @Override
+ public boolean isAttributeDefault(int index) {
+ return mParser.isAttributeDefault(index);
+ }
+
+ @Override
+ public String getAttributeValue(int index) {
+ return mParser.getAttributeValue(index);
+ }
+
+ @Override
+ public String getAttributeValue(String namespace, String name) {
+ return mParser.getAttributeValue(namespace, name);
+ }
+
+ @Override
+ public int getAttributeNameResource(int index) {
+ return 0;
+ }
+
+ @Override
+ public int getAttributeListValue(String namespace, String attribute, String[] options,
+ int defaultValue) {
+ return 0;
+ }
+
+ @Override
+ public boolean getAttributeBooleanValue(String namespace, String attribute,
+ boolean defaultValue) {
+ return false;
+ }
+
+ @Override
+ public int getAttributeResourceValue(String namespace, String attribute, int defaultValue) {
+ return 0;
+ }
+
+ @Override
+ public int getAttributeIntValue(String namespace, String attribute, int defaultValue) {
+ return 0;
+ }
+
+ @Override
+ public int getAttributeUnsignedIntValue(String namespace, String attribute,
+ int defaultValue) {
+ return 0;
+ }
+
+ @Override
+ public float getAttributeFloatValue(String namespace, String attribute,
+ float defaultValue) {
+ return 0;
+ }
+
+ @Override
+ public int getAttributeListValue(int index, String[] options, int defaultValue) {
+ return 0;
+ }
+
+ @Override
+ public boolean getAttributeBooleanValue(int index, boolean defaultValue) {
+ return false;
+ }
+
+ @Override
+ public int getAttributeResourceValue(int index, int defaultValue) {
+ return 0;
+ }
+
+ @Override
+ public int getAttributeIntValue(int index, int defaultValue) {
+ return 0;
+ }
+
+ @Override
+ public int getAttributeUnsignedIntValue(int index, int defaultValue) {
+ return 0;
+ }
+
+ @Override
+ public float getAttributeFloatValue(int index, float defaultValue) {
+ return 0;
+ }
+
+ @Override
+ public String getIdAttribute() {
+ return null;
+ }
+
+ @Override
+ public String getClassAttribute() {
+ return null;
+ }
+
+ @Override
+ public int getIdAttributeResourceValue(int defaultValue) {
+ return 0;
+ }
+
+ @Override
+ public int getStyleAttribute() {
+ return 0;
+ }
+
+ @Override
+ public void close() {
+ }
+
+ @Override
+ public int nextTag() throws IOException, XmlPullParserException {
+ return mParser.nextTag();
+ }
+}