Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 9fd32b89 authored by Lais Andrade's avatar Lais Andrade Committed by Android (Google) Code Review
Browse files

Merge "Introduce system APIs for VibrationEffect XML parser" into main

parents 3aec862d 05332a39
Loading
Loading
Loading
Loading
+13 −0
Original line number Diff line number Diff line
@@ -11471,6 +11471,19 @@ package android.os.storage {
}
package android.os.vibrator.persistence {
  @FlaggedApi("android.os.vibrator.vibration_xml_apis") public final class ParsedVibration {
    method @FlaggedApi("android.os.vibrator.vibration_xml_apis") @Nullable public android.os.VibrationEffect resolve(@NonNull android.os.Vibrator);
  }
  @FlaggedApi("android.os.vibrator.vibration_xml_apis") public final class VibrationXmlParser {
    method @FlaggedApi("android.os.vibrator.vibration_xml_apis") @NonNull public static android.os.vibrator.persistence.ParsedVibration parse(@NonNull java.io.InputStream) throws java.io.IOException;
    method @FlaggedApi("android.os.vibrator.vibration_xml_apis") @NonNull public static android.os.VibrationEffect parseVibrationEffect(@NonNull java.io.InputStream) throws java.io.IOException;
  }
}
package android.permission {
  public final class AdminPermissionControlParams implements android.os.Parcelable {
+11 −8
Original line number Diff line number Diff line
@@ -2761,21 +2761,24 @@ package android.os.vibrator {

package android.os.vibrator.persistence {

  public class ParsedVibration {
    method @NonNull public java.util.List<android.os.VibrationEffect> getVibrationEffects();
    method @Nullable public android.os.VibrationEffect resolve(@NonNull android.os.Vibrator);
  @FlaggedApi("android.os.vibrator.vibration_xml_apis") public final class ParsedVibration {
    ctor public ParsedVibration(@NonNull java.util.List<android.os.VibrationEffect>);
    method @FlaggedApi("android.os.vibrator.vibration_xml_apis") @Nullable public android.os.VibrationEffect resolve(@NonNull android.os.Vibrator);
  }

  public final class VibrationXmlParser {
    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;
  @FlaggedApi("android.os.vibrator.vibration_xml_apis") public final class VibrationXmlParser {
    method @FlaggedApi("android.os.vibrator.vibration_xml_apis") @NonNull public static android.os.vibrator.persistence.ParsedVibration parse(@NonNull java.io.InputStream) throws java.io.IOException;
    method @FlaggedApi("android.os.vibrator.vibration_xml_apis") @NonNull public static android.os.VibrationEffect parseVibrationEffect(@NonNull java.io.InputStream) throws java.io.IOException;
  }

  public static final class VibrationXmlParser.ParseFailedException extends java.io.IOException {
  }

  public final class VibrationXmlSerializer {
    method public static void serialize(@NonNull android.os.VibrationEffect, @NonNull java.io.Writer) throws java.io.IOException, android.os.vibrator.persistence.VibrationXmlSerializer.SerializationFailedException;
    method public static void serialize(@NonNull android.os.VibrationEffect, @NonNull java.io.Writer) throws java.io.IOException;
  }

  public static final class VibrationXmlSerializer.SerializationFailedException extends java.lang.RuntimeException {
  public static final class VibrationXmlSerializer.SerializationFailedException extends java.io.IOException {
  }

}
+10 −0
Original line number Diff line number Diff line
@@ -42,3 +42,13 @@ flag {
        purpose: PURPOSE_BUGFIX
    }
}

flag {
    namespace: "haptics"
    name: "vibration_xml_apis"
    description: "Enabled System APIs for vibration effect XML parser and serializer"
    bug: "347273158"
    metadata {
        purpose: PURPOSE_FEATURE
    }
}
+35 −26
Original line number Diff line number Diff line
@@ -16,31 +16,35 @@

package android.os.vibrator.persistence;

import static android.os.vibrator.Flags.FLAG_VIBRATION_XML_APIS;

import android.annotation.FlaggedApi;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SuppressLint;
import android.annotation.SystemApi;
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;
import java.util.Objects;

/**
 * The result of parsing a serialized vibration, which can be define by one or more
 * {@link VibrationEffect} and a resolution method.
 * The result of parsing a serialized vibration.
 *
 * @see VibrationXmlParser
 *
 * @hide
 */
@TestApi
@SuppressLint("UnflaggedApi") // @TestApi without associated feature.
public class ParsedVibration {
@TestApi // This was used in CTS before the flag was introduced.
@SystemApi
@FlaggedApi(FLAG_VIBRATION_XML_APIS)
public final class ParsedVibration {
    private final List<VibrationEffect> mEffects;

    /** @hide */
    @TestApi
    public ParsedVibration(@NonNull List<VibrationEffect> effects) {
        mEffects = effects;
    }
@@ -49,40 +53,28 @@ public class ParsedVibration {
    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
    @TestApi // This was used in CTS before the flag was introduced.
    @SystemApi
    @FlaggedApi(FLAG_VIBRATION_XML_APIS)
    @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> getVibrationEffects() {
        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) {
    public VibrationEffect resolve(@NonNull VibratorInfo info) {
        for (int i = 0; i < mEffects.size(); i++) {
            VibrationEffect effect = mEffects.get(i);
            if (info.areVibrationFeaturesSupported(effect)) {
@@ -91,4 +83,21 @@ public class ParsedVibration {
        }
        return null;
    }

    @Override
    public boolean equals(@Nullable Object o) {
        if (this == o) {
            return true;
        }
        if (!(o instanceof ParsedVibration)) {
            return false;
        }
        ParsedVibration other = (ParsedVibration) o;
        return mEffects.equals(other.mEffects);
    }

    @Override
    public int hashCode() {
        return Objects.hashCode(mEffects);
    }
}
+123 −88
Original line number Diff line number Diff line
@@ -16,13 +16,15 @@

package android.os.vibrator.persistence;

import static android.os.vibrator.Flags.FLAG_VIBRATION_XML_APIS;

import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SuppressLint;
import android.annotation.SystemApi;
import android.annotation.TestApi;
import android.os.VibrationEffect;
import android.util.Slog;
import android.util.Xml;

import com.android.internal.vibrator.persistence.VibrationEffectXmlParser;
@@ -36,9 +38,12 @@ import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;

import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;

@@ -116,10 +121,10 @@ import java.util.List;
 *
 * @hide
 */
@TestApi
@SuppressLint("UnflaggedApi") // @TestApi without associated feature.
@TestApi // This was used in CTS before the flag was introduced.
@SystemApi
@FlaggedApi(FLAG_VIBRATION_XML_APIS)
public final class VibrationXmlParser {
    private static final String TAG = "VibrationXmlParser";

    /**
     * The MIME type for a xml holding a vibration.
@@ -168,93 +173,109 @@ public final class VibrationXmlParser {
    }

    /**
     * Parses XML content from given input stream into a {@link VibrationEffect}.
     * Parses XML content from given input stream into a {@link ParsedVibration}.
     *
     * <p>It supports both the "vibration-effect" and "vibration-select" root tags.
     * <ul>
     *     <li>If "vibration-effect" is the root tag, the serialization provided 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 with an exception if the content of the input stream does not follow the
     * schema or has unsupported values.
     *
     * @return a {@link ParsedVibration}
     * @throws IOException error reading from given {@link InputStream} or parsing the content.
     *
     * @hide
     */
    @TestApi // Replacing test APIs used in CTS before the flagged system APIs was introduced.
    @SystemApi
    @FlaggedApi(FLAG_VIBRATION_XML_APIS)
    @NonNull
    public static ParsedVibration parse(@NonNull InputStream inputStream) throws IOException {
        return parseDocument(new InputStreamReader(inputStream, StandardCharsets.UTF_8));
    }

    /**
     * Parses XML content from given input stream into a single {@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.
     * serialization. As such, the root tag must be a "vibration-effect" 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.
     * <p>This parser fails with an exception if the content of the input stream does not follow the
     * schema or has unsupported values.
     *
     * @return the {@link VibrationEffect} if parsed successfully, {@code null} otherwise.
     * @throws IOException error reading from given {@link Reader}
     * @return the parsed {@link VibrationEffect}
     * @throws IOException error reading from given {@link InputStream} or parsing the content.
     *
     * @hide
     */
    @TestApi
    @Nullable
    @TestApi // Replacing test APIs used in CTS before the flagged system APIs was introduced.
    @SystemApi
    @FlaggedApi(FLAG_VIBRATION_XML_APIS)
    @NonNull
    public static VibrationEffect parseVibrationEffect(@NonNull InputStream inputStream)
            throws IOException {
        return parseVibrationEffect(new InputStreamReader(inputStream, StandardCharsets.UTF_8));
    }

    /**
     * Parses XML content from given {@link Reader} into a {@link VibrationEffect}.
     *
     * <p>Same as {@link #parseVibrationEffect(InputStream)}, but with a {@link Reader}.
     *
     * @hide
     */
    @NonNull
    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>This method parses an XML content that contains a single, complete {@link VibrationEffect}
     * serialization. As such, the root tag must be a "vibration" tag.
     * Parses XML content from given {@link Reader} into a {@link VibrationEffect}.
     *
     * <p>Same as {@link #parseVibrationEffect(Reader)}, with extra flags to control the parsing
     * behavior.
     *
     * @hide
     */
    @Nullable
    @NonNull
    public static VibrationEffect parseVibrationEffect(@NonNull Reader reader, @Flags int flags)
            throws IOException {
        try {
            return parseDocumentInternal(
                    reader, flags, VibrationXmlParser::parseVibrationEffectInternal);
        } catch (XmlParserException | XmlPullParserException e) {
            Slog.w(TAG, "Error parsing vibration XML", e);
            return null;
        }
        return parseDocumentInternal(reader, flags,
                VibrationXmlParser::parseVibrationEffectInternal);
    }

    /**
     * Parses XML content from given input stream into a {@link ParsedVibration}.
     * Parses XML content from given {@link Reader} 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}
     * <p>Same as {@link #parse(InputStream)}, but with a {@link Reader}.
     *
     * @hide
     */
    @TestApi
    @Nullable
    @NonNull
    public static ParsedVibration parseDocument(@NonNull Reader reader) throws IOException {
        return parseDocument(reader, /* flags= */ 0);
    }

    /**
     * Parses XML content from given input stream into a {@link ParsedVibration}.
     * Parses XML content from given {@link Reader} into a {@link ParsedVibration}.
     *
     * <p>Same as {@link #parseDocument(Reader)}, with extra flags to control the parsing behavior.
     *
     * @hide
     */
    @Nullable
    @NonNull
    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;
        }
    }

    /**
@@ -262,7 +283,7 @@ public final class VibrationXmlParser {
     * {@link ParsedVibration}.
     *
     * <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-effect> or a <vibration-select> start
     * it takes a parser that points to either a "vibration-effect" or a "vibration-select" start
     * tag. No other parser position, including start of document, is considered valid.
     *
     * <p>This method parses until an end "vibration-effect" or "vibration-select" tag (depending
@@ -270,37 +291,22 @@ public final class VibrationXmlParser {
     * 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 ParsedVibration}. The given {@code parser} might be pointing to a child XML tag
     *      that caused the parser failure.
     *         The given {@code parser} might be pointing to a child XML tag that caused the parser
     *         failure.
     *
     * @hide
     */
    @NonNull
    public static ParsedVibration parseElement(@NonNull TypedXmlPullParser parser, @Flags int flags)
            throws IOException, VibrationXmlParserException {
            throws IOException {
        try {
            return parseElementInternal(parser, flags);
        } catch (XmlParserException e) {
            throw new VibrationXmlParserException("Error parsing vibration-select.", e);
        }
    }

    /**
     * Represents an error while parsing a vibration XML input.
     *
     * @hide
     */
    public static final class VibrationXmlParserException extends Exception {
        private VibrationXmlParserException(String message, Throwable cause) {
            super(message, cause);
        }

        private VibrationXmlParserException(String message) {
            super(message);
            throw new ParseFailedException(e);
        }
    }

    @NonNull
    private static ParsedVibration parseElementInternal(
                @NonNull TypedXmlPullParser parser, @Flags int flags)
                        throws IOException, XmlParserException {
@@ -313,11 +319,12 @@ public final class VibrationXmlParser {
            case XmlConstants.TAG_VIBRATION_SELECT:
                return parseVibrationSelectInternal(parser, flags);
            default:
                throw new XmlParserException(
                        "Unexpected tag name when parsing element: " + tagName);
                throw new ParseFailedException(
                        "Unexpected tag " + tagName + " when parsing a vibration");
        }
    }

    @NonNull
    private static ParsedVibration parseVibrationSelectInternal(
            @NonNull TypedXmlPullParser parser, @Flags int flags)
                    throws IOException, XmlParserException {
@@ -332,7 +339,7 @@ public final class VibrationXmlParser {
        return new ParsedVibration(effects);
    }

    /** Parses a single XML element for "vibration" tag into a {@link VibrationEffect}. */
    @NonNull
    private static VibrationEffect parseVibrationEffectInternal(
            @NonNull TypedXmlPullParser parser, @Flags int flags)
                    throws IOException, XmlParserException {
@@ -347,9 +354,11 @@ public final class VibrationXmlParser {
     * This method parses a whole XML document (provided through a {@link Reader}). The root tag is
     * parsed as per a provided {@link ElementParser}.
     */
    @NonNull
    private static <T> T parseDocumentInternal(
            @NonNull Reader reader, @Flags int flags, ElementParser<T> parseLogic)
                    throws IOException, XmlParserException, XmlPullParserException {
            throws IOException {
        try {
            TypedXmlPullParser parser = Xml.newFastPullParser();
            parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true);
            parser.setInput(reader);
@@ -364,15 +373,41 @@ public final class VibrationXmlParser {
            XmlReader.readDocumentEndTag(parser);

            return result;
        } catch (XmlPullParserException e) {
            throw new ParseFailedException("Error initializing XMLPullParser", e);
        } catch (XmlParserException e) {
            throw new ParseFailedException(e);
        }
    }

    /** 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}. */
        @NonNull
        T parse(@NonNull TypedXmlPullParser parser, @Flags int flags)
                throws IOException, XmlParserException;
    }

    /**
     * Represents an error while parsing a vibration XML input.
     *
     * @hide
     */
    @TestApi
    public static final class ParseFailedException extends IOException {
        private ParseFailedException(String message) {
            super(message);
        }

        private ParseFailedException(XmlParserException parserException) {
            this(parserException.getMessage(), parserException);
        }

        private ParseFailedException(String message, Throwable cause) {
            super(message, cause);
        }
    }

    private VibrationXmlParser() {
    }
}
Loading