Loading core/res/res/values/symbols.xml +2 −0 Original line number Diff line number Diff line Loading @@ -5466,6 +5466,8 @@ <!-- For HapticFeedbackConstants configurability defined at HapticFeedbackCustomization --> <java-symbol type="string" name="config_hapticFeedbackCustomizationFile" /> <java-symbol type="xml" name="haptic_feedback_customization" /> <java-symbol type="xml" name="haptic_feedback_customization_source_rotary_encoder" /> <java-symbol type="xml" name="haptic_feedback_customization_source_touchscreen" /> <!-- For ActivityManager PSS profiling configurability --> <java-symbol type="bool" name="config_am_disablePssProfiling" /> Loading core/res/res/xml/haptic_feedback_customization_source_rotary_encoder.xml 0 → 100644 +18 −0 Original line number Diff line number Diff line <?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/> core/res/res/xml/haptic_feedback_customization_source_touchscreen.xml 0 → 100644 +18 −0 Original line number Diff line number Diff line <?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/> services/core/java/com/android/server/vibrator/HapticFeedbackCustomization.java +173 −90 Original line number Diff line number Diff line Loading @@ -16,6 +16,7 @@ package com.android.server.vibrator; import android.annotation.NonNull; import android.annotation.Nullable; import android.content.res.Resources; import android.content.res.XmlResourceParser; Loading @@ -28,7 +29,10 @@ import android.text.TextUtils; import android.util.Slog; import android.util.SparseArray; import android.util.Xml; import android.view.InputDevice; import com.android.internal.R; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.XmlUtils; import com.android.internal.vibrator.persistence.XmlParserException; import com.android.internal.vibrator.persistence.XmlReader; Loading @@ -42,6 +46,7 @@ import java.io.FileNotFoundException; import java.io.FileReader; import java.io.IOException; import java.io.Reader; import java.util.Locale; /** * Class that loads custom {@link VibrationEffect} to be performed for each Loading Loading @@ -92,105 +97,146 @@ final class HapticFeedbackCustomization { private static final String ATTRIBUTE_ID = "id"; /** * Parses the haptic feedback vibration customization XML file for the device, and provides a * mapping of the customized effect IDs to their respective {@link VibrationEffect}s. * * <p>This is potentially expensive, so avoid calling repeatedly. One call is enough, and the * caller should process the returned mapping (if any) for further queries. * * @param res {@link Resources} object to be used for reading the device's resources. * @return a {@link SparseArray} that maps each customized haptic feedback effect ID to its * respective {@link VibrationEffect}, or {@code null}, if the device has not configured * a file for haptic feedback constants customization. * @throws {@link IOException} if an IO error occurs while parsing the customization XML. * @throws {@link CustomizationParserException} for any non-IO error that occurs when parsing * the XML, like an invalid XML content or an invalid haptic feedback constant. * A {@link SparseArray} that maps each customized haptic feedback effect ID to its * respective {@link VibrationEffect}. If this is empty, system's default vibration will be * used. */ @Nullable static SparseArray<VibrationEffect> loadVibrations(Resources res, VibratorInfo vibratorInfo) throws CustomizationParserException, IOException { try { return loadVibrationsInternal(res, vibratorInfo); } catch (VibrationXmlParser.ParseFailedException | XmlParserException | XmlPullParserException e) { throw new CustomizationParserException( "Error parsing haptic feedback customization file.", e); } } @NonNull private final SparseArray<VibrationEffect> mHapticCustomizations; @Nullable private static SparseArray<VibrationEffect> loadVibrationsInternal( Resources res, VibratorInfo vibratorInfo) throws CustomizationParserException, IOException, XmlParserException, XmlPullParserException { /** * A {@link SparseArray} similar to {@link mHapticCustomizations} but for rotary input source * specific customization. */ @NonNull private final SparseArray<VibrationEffect> mHapticCustomizationsForSourceRotary; /** * A {@link SparseArray} similar to {@link mHapticCustomizations} but for touch screen input * source specific customization. */ @NonNull private final SparseArray<VibrationEffect> mHapticCustomizationsForSourceTouchScreen; HapticFeedbackCustomization(Resources res, VibratorInfo vibratorInfo) { if (!Flags.hapticFeedbackVibrationOemCustomizationEnabled()) { Slog.d(TAG, "Haptic feedback customization feature is not enabled."); return null; mHapticCustomizations = new SparseArray<>(); mHapticCustomizationsForSourceRotary = new SparseArray<>(); mHapticCustomizationsForSourceTouchScreen = new SparseArray<>(); return; } // 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; // Load base customizations. SparseArray<VibrationEffect> hapticCustomizations; hapticCustomizations = loadCustomizedFeedbackVibrationFromFile(res, vibratorInfo); if (hapticCustomizations.size() == 0) { // Input source customized haptic feedback was directly added in res. So, no need to old // loading path. hapticCustomizations = loadCustomizedFeedbackVibrationFromRes(res, vibratorInfo, R.xml.haptic_feedback_customization); } mHapticCustomizations = hapticCustomizations; XmlUtils.beginDocument(parser, TAG_CONSTANTS); XmlValidator.checkTagHasNoUnexpectedAttributes(parser); int rootDepth = parser.getDepth(); SparseArray<VibrationEffect> mapping = new SparseArray<>(); while (XmlReader.readNextTagWithin(parser, rootDepth)) { XmlValidator.checkStartTag(parser, TAG_CONSTANT); int customizationDepth = parser.getDepth(); // Load customizations specified by input sources. if (android.os.vibrator.Flags.hapticFeedbackInputSourceCustomizationEnabled()) { mHapticCustomizationsForSourceRotary = loadCustomizedFeedbackVibrationFromRes(res, vibratorInfo, R.xml.haptic_feedback_customization_source_rotary_encoder); mHapticCustomizationsForSourceTouchScreen = loadCustomizedFeedbackVibrationFromRes(res, vibratorInfo, R.xml.haptic_feedback_customization_source_touchscreen); } else { mHapticCustomizationsForSourceRotary = new SparseArray<>(); mHapticCustomizationsForSourceTouchScreen = new SparseArray<>(); } } // Only attribute in tag is the `id` attribute. XmlValidator.checkTagHasNoUnexpectedAttributes(parser, ATTRIBUTE_ID); int effectId = XmlReader.readAttributeIntNonNegative(parser, ATTRIBUTE_ID); if (mapping.contains(effectId)) { throw new CustomizationParserException( "Multiple customizations found for effect " + effectId); @VisibleForTesting HapticFeedbackCustomization(@NonNull SparseArray<VibrationEffect> hapticCustomizations, @NonNull SparseArray<VibrationEffect> hapticCustomizationsForSourceRotary, @NonNull SparseArray<VibrationEffect> hapticCustomizationsForSourceTouchScreen) { mHapticCustomizations = hapticCustomizations; mHapticCustomizationsForSourceRotary = hapticCustomizationsForSourceRotary; mHapticCustomizationsForSourceTouchScreen = hapticCustomizationsForSourceTouchScreen; } // Move the parser one step into the `<constant>` tag. XmlValidator.checkParserCondition( XmlReader.readNextTagWithin(parser, customizationDepth), "Unsupported empty customization tag for effect " + effectId); @Nullable VibrationEffect getEffect(int effectId) { return mHapticCustomizations.get(effectId); } ParsedVibration parsedVibration = VibrationXmlParser.parseElement( parser, VibrationXmlParser.FLAG_ALLOW_HIDDEN_APIS); VibrationEffect effect = parsedVibration.resolve(vibratorInfo); 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)); @Nullable VibrationEffect getEffect(int effectId, int inputSource) { VibrationEffect resultVibration = null; if ((InputDevice.SOURCE_ROTARY_ENCODER & inputSource) != 0) { resultVibration = mHapticCustomizationsForSourceRotary.get(effectId); } else if ((InputDevice.SOURCE_TOUCHSCREEN & inputSource) != 0) { resultVibration = mHapticCustomizationsForSourceTouchScreen.get(effectId); } mapping.put(effectId, effect); if (resultVibration == null) { resultVibration = mHapticCustomizations.get(effectId); } XmlReader.readEndTag(parser, TAG_CONSTANT, customizationDepth); return resultVibration; } // Make checks that the XML ends well. XmlReader.readEndTag(parser, TAG_CONSTANTS, rootDepth); XmlReader.readDocumentEndTag(parser); /** * Parses the haptic feedback vibration customization XML file for the device whose directory is * specified by config. See {@link R.string.config_hapticFeedbackCustomizationFile}. * * @return Return a mapping of the customized effect IDs to their respective * {@link VibrationEffect}s. */ @NonNull private static SparseArray<VibrationEffect> loadCustomizedFeedbackVibrationFromFile( Resources res, VibratorInfo vibratorInfo) { try { TypedXmlPullParser parser = readCustomizationFile(res); if (parser == null) { Slog.d(TAG, "No loadable haptic feedback customization from file."); return new SparseArray<>(); } return parseVibrations(parser, vibratorInfo); } catch (XmlPullParserException | XmlParserException | IOException e) { Slog.e(TAG, "Error parsing haptic feedback customizations from file", e); return new SparseArray<>(); } } return mapping; /** * Parses the haptic feedback vibration customization XML resource for the device. * * @return Return a mapping of the customized effect IDs to their respective * {@link VibrationEffect}s. */ @NonNull private static SparseArray<VibrationEffect> loadCustomizedFeedbackVibrationFromRes( Resources res, VibratorInfo vibratorInfo, int xmlResId) { try { TypedXmlPullParser parser = readCustomizationResources(res, xmlResId); if (parser == null) { Slog.d(TAG, "No loadable haptic feedback customization from res."); return new SparseArray<>(); } return parseVibrations(parser, vibratorInfo); } catch (XmlPullParserException | XmlParserException | IOException e) { Slog.e(TAG, "Error parsing haptic feedback customizations from res", e); return new SparseArray<>(); } } // 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); String customizationFile; try { customizationFile = res.getString( R.string.config_hapticFeedbackCustomizationFile); } catch (Resources.NotFoundException e) { Slog.e(TAG, "Customization file directory config not found.", e); return null; } if (TextUtils.isEmpty(customizationFile)) { return null; } Loading @@ -211,13 +257,14 @@ final class HapticFeedbackCustomization { return parser; } private static TypedXmlPullParser readCustomizationResources(Resources res) { @Nullable private static TypedXmlPullParser readCustomizationResources(Resources res, int xmlResId) { if (!Flags.loadHapticFeedbackVibrationCustomizationFromResources()) { return null; } final XmlResourceParser resParser; try { resParser = res.getXml(com.android.internal.R.xml.haptic_feedback_customization); resParser = res.getXml(xmlResId); } catch (Resources.NotFoundException e) { Slog.e(TAG, "Haptic customization resource not found.", e); return null; Loading @@ -226,16 +273,52 @@ final class HapticFeedbackCustomization { return XmlUtils.makeTyped(resParser); } /** * Represents an error while parsing a haptic feedback customization XML. */ static final class CustomizationParserException extends Exception { private CustomizationParserException(String message) { super(message); @NonNull private static SparseArray<VibrationEffect> parseVibrations(TypedXmlPullParser parser, VibratorInfo vibratorInfo) throws XmlPullParserException, IOException, XmlParserException { XmlUtils.beginDocument(parser, TAG_CONSTANTS); XmlValidator.checkTagHasNoUnexpectedAttributes(parser); int rootDepth = parser.getDepth(); SparseArray<VibrationEffect> mapping = new SparseArray<>(); while (XmlReader.readNextTagWithin(parser, rootDepth)) { XmlValidator.checkStartTag(parser, TAG_CONSTANT); int customizationDepth = parser.getDepth(); // Only attribute in tag is the `id` attribute. XmlValidator.checkTagHasNoUnexpectedAttributes(parser, ATTRIBUTE_ID); int effectId = XmlReader.readAttributeIntNonNegative(parser, ATTRIBUTE_ID); if (mapping.contains(effectId)) { Slog.e(TAG, "Multiple customizations found for effect " + effectId); return new SparseArray<>(); } // Move the parser one step into the `<constant>` tag. XmlValidator.checkParserCondition( XmlReader.readNextTagWithin(parser, customizationDepth), "Unsupported empty customization tag for effect " + effectId); ParsedVibration parsedVibration = VibrationXmlParser.parseElement( parser, VibrationXmlParser.FLAG_ALLOW_HIDDEN_APIS); VibrationEffect effect = parsedVibration.resolve(vibratorInfo); if (effect != null) { if (effect.getDuration() == Long.MAX_VALUE) { Slog.e(TAG, String.format(Locale.getDefault(), "Vibration for effect ID %d is repeating, which is not allowed as a" + " haptic feedback: %s", effectId, effect)); return new SparseArray<>(); } mapping.put(effectId, effect); } private CustomizationParserException(String message, Throwable cause) { super(message, cause); XmlReader.readEndTag(parser, TAG_CONSTANT, customizationDepth); } // Make checks that the XML ends well. XmlReader.readEndTag(parser, TAG_CONSTANTS, rootDepth); XmlReader.readDocumentEndTag(parser); return mapping; } } services/core/java/com/android/server/vibrator/HapticFeedbackVibrationProvider.java +132 −166 File changed.Preview size limit exceeded, changes collapsed. Show changes Loading
core/res/res/values/symbols.xml +2 −0 Original line number Diff line number Diff line Loading @@ -5466,6 +5466,8 @@ <!-- For HapticFeedbackConstants configurability defined at HapticFeedbackCustomization --> <java-symbol type="string" name="config_hapticFeedbackCustomizationFile" /> <java-symbol type="xml" name="haptic_feedback_customization" /> <java-symbol type="xml" name="haptic_feedback_customization_source_rotary_encoder" /> <java-symbol type="xml" name="haptic_feedback_customization_source_touchscreen" /> <!-- For ActivityManager PSS profiling configurability --> <java-symbol type="bool" name="config_am_disablePssProfiling" /> Loading
core/res/res/xml/haptic_feedback_customization_source_rotary_encoder.xml 0 → 100644 +18 −0 Original line number Diff line number Diff line <?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/>
core/res/res/xml/haptic_feedback_customization_source_touchscreen.xml 0 → 100644 +18 −0 Original line number Diff line number Diff line <?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/>
services/core/java/com/android/server/vibrator/HapticFeedbackCustomization.java +173 −90 Original line number Diff line number Diff line Loading @@ -16,6 +16,7 @@ package com.android.server.vibrator; import android.annotation.NonNull; import android.annotation.Nullable; import android.content.res.Resources; import android.content.res.XmlResourceParser; Loading @@ -28,7 +29,10 @@ import android.text.TextUtils; import android.util.Slog; import android.util.SparseArray; import android.util.Xml; import android.view.InputDevice; import com.android.internal.R; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.XmlUtils; import com.android.internal.vibrator.persistence.XmlParserException; import com.android.internal.vibrator.persistence.XmlReader; Loading @@ -42,6 +46,7 @@ import java.io.FileNotFoundException; import java.io.FileReader; import java.io.IOException; import java.io.Reader; import java.util.Locale; /** * Class that loads custom {@link VibrationEffect} to be performed for each Loading Loading @@ -92,105 +97,146 @@ final class HapticFeedbackCustomization { private static final String ATTRIBUTE_ID = "id"; /** * Parses the haptic feedback vibration customization XML file for the device, and provides a * mapping of the customized effect IDs to their respective {@link VibrationEffect}s. * * <p>This is potentially expensive, so avoid calling repeatedly. One call is enough, and the * caller should process the returned mapping (if any) for further queries. * * @param res {@link Resources} object to be used for reading the device's resources. * @return a {@link SparseArray} that maps each customized haptic feedback effect ID to its * respective {@link VibrationEffect}, or {@code null}, if the device has not configured * a file for haptic feedback constants customization. * @throws {@link IOException} if an IO error occurs while parsing the customization XML. * @throws {@link CustomizationParserException} for any non-IO error that occurs when parsing * the XML, like an invalid XML content or an invalid haptic feedback constant. * A {@link SparseArray} that maps each customized haptic feedback effect ID to its * respective {@link VibrationEffect}. If this is empty, system's default vibration will be * used. */ @Nullable static SparseArray<VibrationEffect> loadVibrations(Resources res, VibratorInfo vibratorInfo) throws CustomizationParserException, IOException { try { return loadVibrationsInternal(res, vibratorInfo); } catch (VibrationXmlParser.ParseFailedException | XmlParserException | XmlPullParserException e) { throw new CustomizationParserException( "Error parsing haptic feedback customization file.", e); } } @NonNull private final SparseArray<VibrationEffect> mHapticCustomizations; @Nullable private static SparseArray<VibrationEffect> loadVibrationsInternal( Resources res, VibratorInfo vibratorInfo) throws CustomizationParserException, IOException, XmlParserException, XmlPullParserException { /** * A {@link SparseArray} similar to {@link mHapticCustomizations} but for rotary input source * specific customization. */ @NonNull private final SparseArray<VibrationEffect> mHapticCustomizationsForSourceRotary; /** * A {@link SparseArray} similar to {@link mHapticCustomizations} but for touch screen input * source specific customization. */ @NonNull private final SparseArray<VibrationEffect> mHapticCustomizationsForSourceTouchScreen; HapticFeedbackCustomization(Resources res, VibratorInfo vibratorInfo) { if (!Flags.hapticFeedbackVibrationOemCustomizationEnabled()) { Slog.d(TAG, "Haptic feedback customization feature is not enabled."); return null; mHapticCustomizations = new SparseArray<>(); mHapticCustomizationsForSourceRotary = new SparseArray<>(); mHapticCustomizationsForSourceTouchScreen = new SparseArray<>(); return; } // 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; // Load base customizations. SparseArray<VibrationEffect> hapticCustomizations; hapticCustomizations = loadCustomizedFeedbackVibrationFromFile(res, vibratorInfo); if (hapticCustomizations.size() == 0) { // Input source customized haptic feedback was directly added in res. So, no need to old // loading path. hapticCustomizations = loadCustomizedFeedbackVibrationFromRes(res, vibratorInfo, R.xml.haptic_feedback_customization); } mHapticCustomizations = hapticCustomizations; XmlUtils.beginDocument(parser, TAG_CONSTANTS); XmlValidator.checkTagHasNoUnexpectedAttributes(parser); int rootDepth = parser.getDepth(); SparseArray<VibrationEffect> mapping = new SparseArray<>(); while (XmlReader.readNextTagWithin(parser, rootDepth)) { XmlValidator.checkStartTag(parser, TAG_CONSTANT); int customizationDepth = parser.getDepth(); // Load customizations specified by input sources. if (android.os.vibrator.Flags.hapticFeedbackInputSourceCustomizationEnabled()) { mHapticCustomizationsForSourceRotary = loadCustomizedFeedbackVibrationFromRes(res, vibratorInfo, R.xml.haptic_feedback_customization_source_rotary_encoder); mHapticCustomizationsForSourceTouchScreen = loadCustomizedFeedbackVibrationFromRes(res, vibratorInfo, R.xml.haptic_feedback_customization_source_touchscreen); } else { mHapticCustomizationsForSourceRotary = new SparseArray<>(); mHapticCustomizationsForSourceTouchScreen = new SparseArray<>(); } } // Only attribute in tag is the `id` attribute. XmlValidator.checkTagHasNoUnexpectedAttributes(parser, ATTRIBUTE_ID); int effectId = XmlReader.readAttributeIntNonNegative(parser, ATTRIBUTE_ID); if (mapping.contains(effectId)) { throw new CustomizationParserException( "Multiple customizations found for effect " + effectId); @VisibleForTesting HapticFeedbackCustomization(@NonNull SparseArray<VibrationEffect> hapticCustomizations, @NonNull SparseArray<VibrationEffect> hapticCustomizationsForSourceRotary, @NonNull SparseArray<VibrationEffect> hapticCustomizationsForSourceTouchScreen) { mHapticCustomizations = hapticCustomizations; mHapticCustomizationsForSourceRotary = hapticCustomizationsForSourceRotary; mHapticCustomizationsForSourceTouchScreen = hapticCustomizationsForSourceTouchScreen; } // Move the parser one step into the `<constant>` tag. XmlValidator.checkParserCondition( XmlReader.readNextTagWithin(parser, customizationDepth), "Unsupported empty customization tag for effect " + effectId); @Nullable VibrationEffect getEffect(int effectId) { return mHapticCustomizations.get(effectId); } ParsedVibration parsedVibration = VibrationXmlParser.parseElement( parser, VibrationXmlParser.FLAG_ALLOW_HIDDEN_APIS); VibrationEffect effect = parsedVibration.resolve(vibratorInfo); 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)); @Nullable VibrationEffect getEffect(int effectId, int inputSource) { VibrationEffect resultVibration = null; if ((InputDevice.SOURCE_ROTARY_ENCODER & inputSource) != 0) { resultVibration = mHapticCustomizationsForSourceRotary.get(effectId); } else if ((InputDevice.SOURCE_TOUCHSCREEN & inputSource) != 0) { resultVibration = mHapticCustomizationsForSourceTouchScreen.get(effectId); } mapping.put(effectId, effect); if (resultVibration == null) { resultVibration = mHapticCustomizations.get(effectId); } XmlReader.readEndTag(parser, TAG_CONSTANT, customizationDepth); return resultVibration; } // Make checks that the XML ends well. XmlReader.readEndTag(parser, TAG_CONSTANTS, rootDepth); XmlReader.readDocumentEndTag(parser); /** * Parses the haptic feedback vibration customization XML file for the device whose directory is * specified by config. See {@link R.string.config_hapticFeedbackCustomizationFile}. * * @return Return a mapping of the customized effect IDs to their respective * {@link VibrationEffect}s. */ @NonNull private static SparseArray<VibrationEffect> loadCustomizedFeedbackVibrationFromFile( Resources res, VibratorInfo vibratorInfo) { try { TypedXmlPullParser parser = readCustomizationFile(res); if (parser == null) { Slog.d(TAG, "No loadable haptic feedback customization from file."); return new SparseArray<>(); } return parseVibrations(parser, vibratorInfo); } catch (XmlPullParserException | XmlParserException | IOException e) { Slog.e(TAG, "Error parsing haptic feedback customizations from file", e); return new SparseArray<>(); } } return mapping; /** * Parses the haptic feedback vibration customization XML resource for the device. * * @return Return a mapping of the customized effect IDs to their respective * {@link VibrationEffect}s. */ @NonNull private static SparseArray<VibrationEffect> loadCustomizedFeedbackVibrationFromRes( Resources res, VibratorInfo vibratorInfo, int xmlResId) { try { TypedXmlPullParser parser = readCustomizationResources(res, xmlResId); if (parser == null) { Slog.d(TAG, "No loadable haptic feedback customization from res."); return new SparseArray<>(); } return parseVibrations(parser, vibratorInfo); } catch (XmlPullParserException | XmlParserException | IOException e) { Slog.e(TAG, "Error parsing haptic feedback customizations from res", e); return new SparseArray<>(); } } // 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); String customizationFile; try { customizationFile = res.getString( R.string.config_hapticFeedbackCustomizationFile); } catch (Resources.NotFoundException e) { Slog.e(TAG, "Customization file directory config not found.", e); return null; } if (TextUtils.isEmpty(customizationFile)) { return null; } Loading @@ -211,13 +257,14 @@ final class HapticFeedbackCustomization { return parser; } private static TypedXmlPullParser readCustomizationResources(Resources res) { @Nullable private static TypedXmlPullParser readCustomizationResources(Resources res, int xmlResId) { if (!Flags.loadHapticFeedbackVibrationCustomizationFromResources()) { return null; } final XmlResourceParser resParser; try { resParser = res.getXml(com.android.internal.R.xml.haptic_feedback_customization); resParser = res.getXml(xmlResId); } catch (Resources.NotFoundException e) { Slog.e(TAG, "Haptic customization resource not found.", e); return null; Loading @@ -226,16 +273,52 @@ final class HapticFeedbackCustomization { return XmlUtils.makeTyped(resParser); } /** * Represents an error while parsing a haptic feedback customization XML. */ static final class CustomizationParserException extends Exception { private CustomizationParserException(String message) { super(message); @NonNull private static SparseArray<VibrationEffect> parseVibrations(TypedXmlPullParser parser, VibratorInfo vibratorInfo) throws XmlPullParserException, IOException, XmlParserException { XmlUtils.beginDocument(parser, TAG_CONSTANTS); XmlValidator.checkTagHasNoUnexpectedAttributes(parser); int rootDepth = parser.getDepth(); SparseArray<VibrationEffect> mapping = new SparseArray<>(); while (XmlReader.readNextTagWithin(parser, rootDepth)) { XmlValidator.checkStartTag(parser, TAG_CONSTANT); int customizationDepth = parser.getDepth(); // Only attribute in tag is the `id` attribute. XmlValidator.checkTagHasNoUnexpectedAttributes(parser, ATTRIBUTE_ID); int effectId = XmlReader.readAttributeIntNonNegative(parser, ATTRIBUTE_ID); if (mapping.contains(effectId)) { Slog.e(TAG, "Multiple customizations found for effect " + effectId); return new SparseArray<>(); } // Move the parser one step into the `<constant>` tag. XmlValidator.checkParserCondition( XmlReader.readNextTagWithin(parser, customizationDepth), "Unsupported empty customization tag for effect " + effectId); ParsedVibration parsedVibration = VibrationXmlParser.parseElement( parser, VibrationXmlParser.FLAG_ALLOW_HIDDEN_APIS); VibrationEffect effect = parsedVibration.resolve(vibratorInfo); if (effect != null) { if (effect.getDuration() == Long.MAX_VALUE) { Slog.e(TAG, String.format(Locale.getDefault(), "Vibration for effect ID %d is repeating, which is not allowed as a" + " haptic feedback: %s", effectId, effect)); return new SparseArray<>(); } mapping.put(effectId, effect); } private CustomizationParserException(String message, Throwable cause) { super(message, cause); XmlReader.readEndTag(parser, TAG_CONSTANT, customizationDepth); } // Make checks that the XML ends well. XmlReader.readEndTag(parser, TAG_CONSTANTS, rootDepth); XmlReader.readDocumentEndTag(parser); return mapping; } }
services/core/java/com/android/server/vibrator/HapticFeedbackVibrationProvider.java +132 −166 File changed.Preview size limit exceeded, changes collapsed. Show changes