Loading core/java/android/os/vibrator/flags.aconfig +10 −0 Original line number Diff line number Diff line Loading @@ -136,3 +136,13 @@ flag { purpose: PURPOSE_FEATURE } } flag { namespace: "haptics" name: "vibration_thread_handling_hal_failure" description: "Fixes how VibrationThread handled HAL failures" bug: "419572960" metadata { purpose: PURPOSE_BUGFIX } } core/proto/android/server/vibrator/vibratormanagerservice.proto +2 −1 Original line number Diff line number Diff line Loading @@ -145,9 +145,10 @@ message VibrationProto { IGNORED_FROM_VIRTUAL_DEVICE = 26; IGNORED_ON_WIRELESS_CHARGER = 27; IGNORED_MISSING_PERMISSION = 28; IGNORED_INVALID_REQUEST = 31; CANCELLED_BY_APP_OPS = 29; CANCELLED_BY_FOREGROUND_USER = 30; IGNORED_INVALID_REQUEST = 31; IGNORED_ERROR_DISPATCHING = 32; reserved 17; // prev IGNORED_UNKNOWN_VIBRATION } } Loading services/core/java/com/android/server/vibrator/AbstractComposedVibratorStep.java +41 −1 Original line number Diff line number Diff line Loading @@ -18,6 +18,7 @@ package com.android.server.vibrator; import android.os.SystemClock; import android.os.VibrationEffect; import android.os.vibrator.Flags; import java.util.List; Loading Loading @@ -48,12 +49,45 @@ abstract class AbstractComposedVibratorStep extends AbstractVibratorStep { this.segmentIndex = index; } /** * Return the {@link VibrationStepConductor#nextVibrateStep} to start right away, skipping the * current segment from the effect. */ protected List<Step> skipStep() { return Flags.vibrationThreadHandlingHalFailure() ? skipStep(SystemClock.uptimeMillis()) // Preserve old behavior when fix is not enabled. : vibratorOnNextSteps(/* segmentsPlayed= */ 1); } /** * Return the {@link VibrationStepConductor#nextVibrateStep} to start at given time, skipping * the current segment from the effect. */ protected List<Step> skipStep(long nextStartTime) { return nextSteps(nextStartTime, /* segmentsPlayed= */ 1); } /** * Return the {@link VibrationStepConductor#nextVibrateStep} with start and off timings * calculated from {@link #getVibratorOnDuration()} based on the current * {@link SystemClock#uptimeMillis()} and jumping all played segments from the effect. * * <p>This should be used when the vibrator result is responsible for the step execution timing, * and it will cancel the playback if the HAL result is unsupported or failure. */ protected List<Step> nextSteps(int segmentsPlayed) { protected List<Step> vibratorOnNextSteps(int segmentsPlayed) { if (Flags.vibrationThreadHandlingHalFailure()) { if (mVibratorOnResult > 0) { // Vibrator was turned on by this step, with mVibratorOnResult as the duration. // Schedule next steps for right after the vibration finishes. long nextStartTime = SystemClock.uptimeMillis() + mVibratorOnResult; return nextSteps(nextStartTime, segmentsPlayed); } else { // Step unsupported or failed, cancel the vibration on this vibrator. return cancelStep(); } } // Schedule next steps to run right away. long nextStartTime = SystemClock.uptimeMillis(); if (mVibratorOnResult > 0) { Loading Loading @@ -86,4 +120,10 @@ abstract class AbstractComposedVibratorStep extends AbstractVibratorStep { nextSegmentIndex, mPendingVibratorOffDeadline); return List.of(nextStep); } /** Return next steps for cancelling the vibration playback. */ protected List<Step> cancelStep() { return List.of(new CompleteEffectVibratorStep(conductor, SystemClock.uptimeMillis(), /* cancelled= */ true, controller, /* pendingVibratorOffDeadline= */ 0)); } } services/core/java/com/android/server/vibrator/ComposePrimitivesVibratorStep.java +2 −5 Original line number Diff line number Diff line Loading @@ -62,8 +62,7 @@ final class ComposePrimitivesVibratorStep extends AbstractComposedVibratorStep { if (primitives.isEmpty()) { Slog.w(VibrationThread.TAG, "Ignoring wrong segment for a ComposePrimitivesStep: " + effect.getSegments().get(segmentIndex)); // Skip this step and play the next one right away. return nextSteps(/* segmentsPlayed= */ 1); return skipStep(); } if (VibrationThread.DEBUG) { Loading @@ -77,9 +76,7 @@ final class ComposePrimitivesVibratorStep extends AbstractComposedVibratorStep { long vibratorOnResult = controller.on(primitivesArray, getVibration().id, stepId); handleVibratorOnResult(vibratorOnResult); getVibration().stats.reportComposePrimitives(vibratorOnResult, primitivesArray); // The next start and off times will be calculated from mVibratorOnResult. return nextSteps(/* segmentsPlayed= */ primitives.size()); return vibratorOnNextSteps(/* segmentsPlayed= */ primitives.size()); } finally { Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR); } Loading services/core/java/com/android/server/vibrator/ComposePwleV2VibratorStep.java +3 −7 Original line number Diff line number Diff line Loading @@ -49,8 +49,7 @@ final class ComposePwleV2VibratorStep extends AbstractComposedVibratorStep { @Override public List<Step> play() { if (!Flags.normalizedPwleEffects()) { // Skip this step and play the next one right away. return nextSteps(/* segmentsPlayed= */ 1); return skipStep(); } Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "ComposePwleV2Step"); Loading @@ -63,8 +62,7 @@ final class ComposePwleV2VibratorStep extends AbstractComposedVibratorStep { if (pwles.isEmpty()) { Slog.w(VibrationThread.TAG, "Ignoring wrong segment for a ComposeEnvelopeStep: " + effect.getSegments().get(segmentIndex)); // Skip this step and play the next one right away. return nextSteps(/* segmentsPlayed= */ 1); return skipStep(); } if (VibrationThread.DEBUG) { Loading @@ -76,9 +74,7 @@ final class ComposePwleV2VibratorStep extends AbstractComposedVibratorStep { long vibratorOnResult = controller.on(pwlesArray, getVibration().id, stepId); handleVibratorOnResult(vibratorOnResult); getVibration().stats.reportComposePwle(vibratorOnResult, pwlesArray); // The next start and off times will be calculated from mVibratorOnResult. return nextSteps(/* segmentsPlayed= */ pwles.size()); return vibratorOnNextSteps(/* segmentsPlayed= */ pwles.size()); } finally { Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR); } Loading Loading
core/java/android/os/vibrator/flags.aconfig +10 −0 Original line number Diff line number Diff line Loading @@ -136,3 +136,13 @@ flag { purpose: PURPOSE_FEATURE } } flag { namespace: "haptics" name: "vibration_thread_handling_hal_failure" description: "Fixes how VibrationThread handled HAL failures" bug: "419572960" metadata { purpose: PURPOSE_BUGFIX } }
core/proto/android/server/vibrator/vibratormanagerservice.proto +2 −1 Original line number Diff line number Diff line Loading @@ -145,9 +145,10 @@ message VibrationProto { IGNORED_FROM_VIRTUAL_DEVICE = 26; IGNORED_ON_WIRELESS_CHARGER = 27; IGNORED_MISSING_PERMISSION = 28; IGNORED_INVALID_REQUEST = 31; CANCELLED_BY_APP_OPS = 29; CANCELLED_BY_FOREGROUND_USER = 30; IGNORED_INVALID_REQUEST = 31; IGNORED_ERROR_DISPATCHING = 32; reserved 17; // prev IGNORED_UNKNOWN_VIBRATION } } Loading
services/core/java/com/android/server/vibrator/AbstractComposedVibratorStep.java +41 −1 Original line number Diff line number Diff line Loading @@ -18,6 +18,7 @@ package com.android.server.vibrator; import android.os.SystemClock; import android.os.VibrationEffect; import android.os.vibrator.Flags; import java.util.List; Loading Loading @@ -48,12 +49,45 @@ abstract class AbstractComposedVibratorStep extends AbstractVibratorStep { this.segmentIndex = index; } /** * Return the {@link VibrationStepConductor#nextVibrateStep} to start right away, skipping the * current segment from the effect. */ protected List<Step> skipStep() { return Flags.vibrationThreadHandlingHalFailure() ? skipStep(SystemClock.uptimeMillis()) // Preserve old behavior when fix is not enabled. : vibratorOnNextSteps(/* segmentsPlayed= */ 1); } /** * Return the {@link VibrationStepConductor#nextVibrateStep} to start at given time, skipping * the current segment from the effect. */ protected List<Step> skipStep(long nextStartTime) { return nextSteps(nextStartTime, /* segmentsPlayed= */ 1); } /** * Return the {@link VibrationStepConductor#nextVibrateStep} with start and off timings * calculated from {@link #getVibratorOnDuration()} based on the current * {@link SystemClock#uptimeMillis()} and jumping all played segments from the effect. * * <p>This should be used when the vibrator result is responsible for the step execution timing, * and it will cancel the playback if the HAL result is unsupported or failure. */ protected List<Step> nextSteps(int segmentsPlayed) { protected List<Step> vibratorOnNextSteps(int segmentsPlayed) { if (Flags.vibrationThreadHandlingHalFailure()) { if (mVibratorOnResult > 0) { // Vibrator was turned on by this step, with mVibratorOnResult as the duration. // Schedule next steps for right after the vibration finishes. long nextStartTime = SystemClock.uptimeMillis() + mVibratorOnResult; return nextSteps(nextStartTime, segmentsPlayed); } else { // Step unsupported or failed, cancel the vibration on this vibrator. return cancelStep(); } } // Schedule next steps to run right away. long nextStartTime = SystemClock.uptimeMillis(); if (mVibratorOnResult > 0) { Loading Loading @@ -86,4 +120,10 @@ abstract class AbstractComposedVibratorStep extends AbstractVibratorStep { nextSegmentIndex, mPendingVibratorOffDeadline); return List.of(nextStep); } /** Return next steps for cancelling the vibration playback. */ protected List<Step> cancelStep() { return List.of(new CompleteEffectVibratorStep(conductor, SystemClock.uptimeMillis(), /* cancelled= */ true, controller, /* pendingVibratorOffDeadline= */ 0)); } }
services/core/java/com/android/server/vibrator/ComposePrimitivesVibratorStep.java +2 −5 Original line number Diff line number Diff line Loading @@ -62,8 +62,7 @@ final class ComposePrimitivesVibratorStep extends AbstractComposedVibratorStep { if (primitives.isEmpty()) { Slog.w(VibrationThread.TAG, "Ignoring wrong segment for a ComposePrimitivesStep: " + effect.getSegments().get(segmentIndex)); // Skip this step and play the next one right away. return nextSteps(/* segmentsPlayed= */ 1); return skipStep(); } if (VibrationThread.DEBUG) { Loading @@ -77,9 +76,7 @@ final class ComposePrimitivesVibratorStep extends AbstractComposedVibratorStep { long vibratorOnResult = controller.on(primitivesArray, getVibration().id, stepId); handleVibratorOnResult(vibratorOnResult); getVibration().stats.reportComposePrimitives(vibratorOnResult, primitivesArray); // The next start and off times will be calculated from mVibratorOnResult. return nextSteps(/* segmentsPlayed= */ primitives.size()); return vibratorOnNextSteps(/* segmentsPlayed= */ primitives.size()); } finally { Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR); } Loading
services/core/java/com/android/server/vibrator/ComposePwleV2VibratorStep.java +3 −7 Original line number Diff line number Diff line Loading @@ -49,8 +49,7 @@ final class ComposePwleV2VibratorStep extends AbstractComposedVibratorStep { @Override public List<Step> play() { if (!Flags.normalizedPwleEffects()) { // Skip this step and play the next one right away. return nextSteps(/* segmentsPlayed= */ 1); return skipStep(); } Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "ComposePwleV2Step"); Loading @@ -63,8 +62,7 @@ final class ComposePwleV2VibratorStep extends AbstractComposedVibratorStep { if (pwles.isEmpty()) { Slog.w(VibrationThread.TAG, "Ignoring wrong segment for a ComposeEnvelopeStep: " + effect.getSegments().get(segmentIndex)); // Skip this step and play the next one right away. return nextSteps(/* segmentsPlayed= */ 1); return skipStep(); } if (VibrationThread.DEBUG) { Loading @@ -76,9 +74,7 @@ final class ComposePwleV2VibratorStep extends AbstractComposedVibratorStep { long vibratorOnResult = controller.on(pwlesArray, getVibration().id, stepId); handleVibratorOnResult(vibratorOnResult); getVibration().stats.reportComposePwle(vibratorOnResult, pwlesArray); // The next start and off times will be calculated from mVibratorOnResult. return nextSteps(/* segmentsPlayed= */ pwles.size()); return vibratorOnNextSteps(/* segmentsPlayed= */ pwles.size()); } finally { Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR); } Loading