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

Commit b5219782 authored by Treehugger Robot's avatar Treehugger Robot Committed by Android (Google) Code Review
Browse files

Merge "Introduce vibrator service effect pipeline support" into main

parents 25fbada7 9bc0f1eb
Loading
Loading
Loading
Loading
+59 −3
Original line number Diff line number Diff line
@@ -17,6 +17,7 @@
package android.os;

import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.TestApi;
import android.os.vibrator.Flags;
import android.util.SparseArray;
@@ -28,6 +29,7 @@ import java.util.List;
import java.util.Locale;
import java.util.Objects;
import java.util.StringJoiner;
import java.util.function.Function;

/**
 * A CombinedVibration describes a combination of haptic effects to be performed by one or more
@@ -113,6 +115,17 @@ public abstract class CombinedVibration implements Parcelable {
    @TestApi
    public abstract long getDuration();

    /**
     * Gets the estimated duration of the combined vibration in milliseconds.
     *
     * <p>For effects with hardware-dependent constants (e.g. primitive compositions), this returns
     * the estimated duration based on the {@link VibratorInfo}. For all other effects this will
     * return the same as {@link #getDuration()}.
     *
     * @hide
     */
    public abstract long getDuration(@Nullable SparseArray<VibratorInfo> vibratorInfos);

    /**
     * Returns true if this effect could represent a touch haptic feedback.
     *
@@ -381,6 +394,23 @@ public abstract class CombinedVibration implements Parcelable {
            return mEffect.getDuration();
        }

        /** @hide */
        @Override
        public long getDuration(@Nullable SparseArray<VibratorInfo> vibratorInfos) {
            if (vibratorInfos == null) {
                return getDuration();
            }
            long maxDuration = 0;
            for (int i = 0; i < vibratorInfos.size(); i++) {
                long duration = mEffect.getDuration(vibratorInfos.valueAt(i));
                if ((duration == Long.MAX_VALUE) || (duration < 0)) {
                    return duration;
                }
                maxDuration = Math.max(maxDuration, duration);
            }
            return maxDuration;
        }

        /** @hide */
        @Override
        public boolean isHapticFeedbackCandidate() {
@@ -531,10 +561,27 @@ public abstract class CombinedVibration implements Parcelable {

        @Override
        public long getDuration() {
            return getDuration(idx -> mEffects.valueAt(idx).getDuration());
        }

        /** @hide */
        @Override
        public long getDuration(@Nullable SparseArray<VibratorInfo> vibratorInfos) {
            if (vibratorInfos == null) {
                return getDuration();
            }
            return getDuration(idx -> {
                VibrationEffect effect = mEffects.valueAt(idx);
                VibratorInfo info = vibratorInfos.get(mEffects.keyAt(idx));
                return effect.getDuration(info);
            });
        }

        private long getDuration(Function<Integer, Long> durationFn) {
            long maxDuration = Long.MIN_VALUE;
            boolean hasUnknownStep = false;
            for (int i = 0; i < mEffects.size(); i++) {
                long duration = mEffects.valueAt(i).getDuration();
                long duration = durationFn.apply(i);
                if (duration == Long.MAX_VALUE) {
                    // If any duration is repeating, this combination duration is also repeating.
                    return duration;
@@ -750,12 +797,21 @@ public abstract class CombinedVibration implements Parcelable {

        @Override
        public long getDuration() {
            return getDuration(CombinedVibration::getDuration);
        }

        /** @hide */
        @Override
        public long getDuration(@Nullable SparseArray<VibratorInfo> vibratorInfos) {
            return getDuration(effect -> effect.getDuration(vibratorInfos));
        }

        private long getDuration(Function<CombinedVibration, Long> durationFn) {
            boolean hasUnknownStep = false;
            long durations = 0;
            final int effectCount = mEffects.size();
            for (int i = 0; i < effectCount; i++) {
                CombinedVibration effect = mEffects.get(i);
                long duration = effect.getDuration();
                long duration = durationFn.apply(mEffects.get(i));
                if (duration == Long.MAX_VALUE) {
                    // If any duration is repeating, this combination duration is also repeating.
                    return duration;
+25 −1
Original line number Diff line number Diff line
@@ -55,6 +55,7 @@ import java.util.Locale;
import java.util.Objects;
import java.util.StringJoiner;
import java.util.function.BiFunction;
import java.util.function.Function;

/**
 * A VibrationEffect describes a haptic effect to be performed by a {@link Vibrator}.
@@ -564,6 +565,19 @@ public abstract class VibrationEffect implements Parcelable {
    @TestApi
    public abstract long getDuration();

    /**
     * Gets the estimated duration of the segment for given vibrator, in milliseconds.
     *
     * <p>For effects with hardware-dependent constants (e.g. primitive compositions), this returns
     * the estimated duration based on the given {@link VibratorInfo}. For all other effects this
     * will return the same as {@link #getDuration()}.
     *
     * @hide
     */
    public long getDuration(@Nullable VibratorInfo vibratorInfo) {
        return getDuration();
    }

    /**
     * Checks if a vibrator with a given {@link VibratorInfo} can play this effect as intended.
     *
@@ -904,13 +918,23 @@ public abstract class VibrationEffect implements Parcelable {

        @Override
        public long getDuration() {
            return getDuration(VibrationEffectSegment::getDuration);
        }

        /** @hide */
        @Override
        public long getDuration(@Nullable VibratorInfo vibratorInfo) {
            return getDuration(segment -> segment.getDuration(vibratorInfo));
        }

        private long getDuration(Function<VibrationEffectSegment, Long> durationFn) {
            if (mRepeatIndex >= 0) {
                return Long.MAX_VALUE;
            }
            int segmentCount = mSegments.size();
            long totalDuration = 0;
            for (int i = 0; i < segmentCount; i++) {
                long segmentDuration = mSegments.get(i).getDuration();
                long segmentDuration = durationFn.apply(mSegments.get(i));
                if (segmentDuration < 0) {
                    return segmentDuration;
                }
+69 −38
Original line number Diff line number Diff line
@@ -16,6 +16,17 @@

package android.os.vibrator;

import static android.os.VibrationEffect.Composition.PRIMITIVE_CLICK;
import static android.os.VibrationEffect.Composition.PRIMITIVE_THUD;
import static android.os.VibrationEffect.Composition.PRIMITIVE_TICK;
import static android.os.VibrationEffect.EFFECT_CLICK;
import static android.os.VibrationEffect.EFFECT_DOUBLE_CLICK;
import static android.os.VibrationEffect.EFFECT_HEAVY_CLICK;
import static android.os.VibrationEffect.EFFECT_POP;
import static android.os.VibrationEffect.EFFECT_TEXTURE_TICK;
import static android.os.VibrationEffect.EFFECT_THUD;
import static android.os.VibrationEffect.EFFECT_TICK;

import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.TestApi;
@@ -76,6 +87,32 @@ public final class PrebakedSegment extends VibrationEffectSegment {
        return -1;
    }

    /** @hide */
    @Override
    public long getDuration(@Nullable VibratorInfo vibratorInfo) {
        if (vibratorInfo == null) {
            return getDuration();
        }
        return switch (mEffectId) {
            case EFFECT_TICK,
                 EFFECT_CLICK,
                 EFFECT_HEAVY_CLICK -> estimateFromPrimitiveDuration(vibratorInfo, PRIMITIVE_CLICK);
            case EFFECT_TEXTURE_TICK -> estimateFromPrimitiveDuration(vibratorInfo, PRIMITIVE_TICK);
            case EFFECT_THUD -> estimateFromPrimitiveDuration(vibratorInfo, PRIMITIVE_THUD);
            case EFFECT_DOUBLE_CLICK -> {
                long clickDuration = vibratorInfo.getPrimitiveDuration(PRIMITIVE_CLICK);
                yield clickDuration > 0 ? 2 * clickDuration : getDuration();
            }
            default -> getDuration();
        };
    }

    private long estimateFromPrimitiveDuration(VibratorInfo vibratorInfo, int primitiveId) {
        int duration = vibratorInfo.getPrimitiveDuration(primitiveId);
        // Unsupported primitives should be ignored here.
        return duration > 0 ? duration : getDuration();
    }

    /** @hide */
    @Override
    public boolean areVibrationFeaturesSupported(@NonNull VibratorInfo vibratorInfo) {
@@ -89,34 +126,30 @@ public final class PrebakedSegment extends VibrationEffectSegment {
        }
        // The vibrator does not have hardware support for the effect, but the effect has fallback
        // support. Check if a fallback will be available for the effect ID.
        switch (mEffectId) {
            case VibrationEffect.EFFECT_CLICK:
            case VibrationEffect.EFFECT_DOUBLE_CLICK:
            case VibrationEffect.EFFECT_HEAVY_CLICK:
            case VibrationEffect.EFFECT_TICK:
        return switch (mEffectId) {
            // Any of these effects are always supported via some form of fallback.
                return true;
            default:
                return false;
        }
            case EFFECT_CLICK,
                 EFFECT_DOUBLE_CLICK,
                 EFFECT_HEAVY_CLICK,
                 EFFECT_TICK -> true;
            default -> false;
        };
    }

    /** @hide */
    @Override
    public boolean isHapticFeedbackCandidate() {
        switch (mEffectId) {
            case VibrationEffect.EFFECT_CLICK:
            case VibrationEffect.EFFECT_DOUBLE_CLICK:
            case VibrationEffect.EFFECT_HEAVY_CLICK:
            case VibrationEffect.EFFECT_POP:
            case VibrationEffect.EFFECT_TEXTURE_TICK:
            case VibrationEffect.EFFECT_THUD:
            case VibrationEffect.EFFECT_TICK:
                return true;
            default:
        return switch (mEffectId) {
            case EFFECT_CLICK,
                 EFFECT_DOUBLE_CLICK,
                 EFFECT_HEAVY_CLICK,
                 EFFECT_POP,
                 EFFECT_TEXTURE_TICK,
                 EFFECT_THUD,
                 EFFECT_TICK -> true;
            // VibrationEffect.RINGTONES are not segments that could represent a haptic feedback
                return false;
        }
            default -> false;
        };
    }

    /** @hide */
@@ -153,27 +186,25 @@ public final class PrebakedSegment extends VibrationEffectSegment {
    }

    private static boolean isValidEffectStrength(int strength) {
        switch (strength) {
            case VibrationEffect.EFFECT_STRENGTH_LIGHT:
            case VibrationEffect.EFFECT_STRENGTH_MEDIUM:
            case VibrationEffect.EFFECT_STRENGTH_STRONG:
                return true;
            default:
                return false;
        }
        return switch (strength) {
            case VibrationEffect.EFFECT_STRENGTH_LIGHT,
                 VibrationEffect.EFFECT_STRENGTH_MEDIUM,
                 VibrationEffect.EFFECT_STRENGTH_STRONG -> true;
            default -> false;
        };
    }

    /** @hide */
    @Override
    public void validate() {
        switch (mEffectId) {
            case VibrationEffect.EFFECT_CLICK:
            case VibrationEffect.EFFECT_DOUBLE_CLICK:
            case VibrationEffect.EFFECT_HEAVY_CLICK:
            case VibrationEffect.EFFECT_POP:
            case VibrationEffect.EFFECT_TEXTURE_TICK:
            case VibrationEffect.EFFECT_THUD:
            case VibrationEffect.EFFECT_TICK:
            case EFFECT_CLICK:
            case EFFECT_DOUBLE_CLICK:
            case EFFECT_HEAVY_CLICK:
            case EFFECT_POP:
            case EFFECT_TEXTURE_TICK:
            case EFFECT_THUD:
            case EFFECT_TICK:
                break;
            default:
                int[] ringtones = VibrationEffect.RINGTONES;
+10 −0
Original line number Diff line number Diff line
@@ -75,6 +75,16 @@ public final class PrimitiveSegment extends VibrationEffectSegment {
        return -1;
    }

    /** @hide */
    @Override
    public long getDuration(@Nullable VibratorInfo vibratorInfo) {
        if (vibratorInfo == null) {
            return getDuration();
        }
        int duration = vibratorInfo.getPrimitiveDuration(mPrimitiveId);
        return duration > 0 ? duration + mDelay : getDuration();
    }

    /** @hide */
    @Override
    public boolean areVibrationFeaturesSupported(@NonNull VibratorInfo vibratorInfo) {
+20 −0
Original line number Diff line number Diff line
@@ -86,6 +86,7 @@ public class VibrationConfig {
    private final int mDefaultKeyboardVibrationIntensity;

    private final boolean mKeyboardVibrationSettingsSupported;
    private final int mVibrationPipelineMaxDurationMs;

    /** @hide */
    public VibrationConfig(@Nullable Resources resources) {
@@ -106,6 +107,8 @@ public class VibrationConfig {
                com.android.internal.R.bool.config_ignoreVibrationsOnWirelessCharger);
        mKeyboardVibrationSettingsSupported = loadBoolean(resources,
                com.android.internal.R.bool.config_keyboardVibrationSettingsSupported);
        mVibrationPipelineMaxDurationMs = loadInteger(resources,
                com.android.internal.R.integer.config_vibrationPipelineMaxDuration, 0);

        mDefaultAlarmVibrationIntensity = loadDefaultIntensity(resources,
                com.android.internal.R.integer.config_defaultAlarmVibrationIntensity);
@@ -220,6 +223,23 @@ public class VibrationConfig {
        return mRampStepDurationMs;
    }

    /**
     * The max duration, in milliseconds, allowed for pipelining vibration requests.
     *
     * <p>If the ongoing vibration duration is shorter than this threshold then it should be allowed
     * to finish before the next vibration can start. If the ongoing vibration is longer than this
     * then it should be cancelled when it's superseded for the new one.
     *
     * @return the max duration allowed for vibration effect to finish before the next request, or
     * zero to disable effect pipelining.
     */
    public int getVibrationPipelineMaxDurationMs() {
        if (mVibrationPipelineMaxDurationMs < 0) {
            return 0;
        }
        return mVibrationPipelineMaxDurationMs;
    }

    /**
     * Whether or not vibrations are ignored if the device is on a wireless charger.
     *
Loading