blob: 90b6f95f8740711d001574ff78514181b9c1c0eb [file] [log] [blame]
/*
* Copyright (C) 2022 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.os.SystemClock;
import android.os.VibrationEffect;
import android.util.Slog;
import java.util.Arrays;
import java.util.List;
/**
* Represent a step on a single vibrator that plays one or more segments from a
* {@link VibrationEffect.Composed} effect.
*/
abstract class AbstractVibratorStep extends Step {
public final VibratorController controller;
public final VibrationEffect.Composed effect;
public final int segmentIndex;
long mVibratorOnResult;
long mPendingVibratorOffDeadline;
boolean mVibratorCompleteCallbackReceived;
/**
* @param conductor The VibrationStepConductor for these steps.
* @param startTime The time to schedule this step in the
* {@link VibrationStepConductor}.
* @param controller The vibrator that is playing the effect.
* @param effect The effect being played in this step.
* @param index The index of the next segment to be played by this step
* @param pendingVibratorOffDeadline The time the vibrator is expected to complete any
* previous vibration and turn off. This is used to allow this step to
* be triggered when the completion callback is received, and can
* be used to play effects back-to-back.
*/
AbstractVibratorStep(VibrationStepConductor conductor, long startTime,
VibratorController controller, VibrationEffect.Composed effect, int index,
long pendingVibratorOffDeadline) {
super(conductor, startTime);
this.controller = controller;
this.effect = effect;
this.segmentIndex = index;
mPendingVibratorOffDeadline = pendingVibratorOffDeadline;
}
public int getVibratorId() {
return controller.getVibratorInfo().getId();
}
@Override
public long getVibratorOnDuration() {
return mVibratorOnResult;
}
@Override
public boolean acceptVibratorCompleteCallback(int vibratorId) {
if (getVibratorId() != vibratorId) {
return false;
}
// Only activate this step if a timeout was set to wait for the vibration to complete,
// otherwise we are waiting for the correct time to play the next step.
boolean shouldAcceptCallback = mPendingVibratorOffDeadline > SystemClock.uptimeMillis();
if (VibrationThread.DEBUG) {
Slog.d(VibrationThread.TAG,
"Received completion callback from " + vibratorId
+ ", accepted = " + shouldAcceptCallback);
}
// The callback indicates this vibrator has stopped, reset the timeout.
mPendingVibratorOffDeadline = 0;
mVibratorCompleteCallbackReceived = true;
return shouldAcceptCallback;
}
@Override
public List<Step> cancel() {
return Arrays.asList(new CompleteEffectVibratorStep(conductor, SystemClock.uptimeMillis(),
/* cancelled= */ true, controller, mPendingVibratorOffDeadline));
}
@Override
public void cancelImmediately() {
if (mPendingVibratorOffDeadline > SystemClock.uptimeMillis()) {
// Vibrator might be running from previous steps, so turn it off while canceling.
stopVibrating();
}
}
protected long handleVibratorOnResult(long vibratorOnResult) {
mVibratorOnResult = vibratorOnResult;
if (VibrationThread.DEBUG) {
Slog.d(VibrationThread.TAG,
"Turned on vibrator " + getVibratorId() + ", result = " + mVibratorOnResult);
}
if (mVibratorOnResult > 0) {
// Vibrator was turned on by this step, with vibratorOnResult as the duration.
// Set an extra timeout to wait for the vibrator completion callback.
mPendingVibratorOffDeadline = SystemClock.uptimeMillis() + mVibratorOnResult
+ VibrationStepConductor.CALLBACKS_EXTRA_TIMEOUT;
} else {
// Vibrator does not support the request or failed to turn on, reset callback deadline.
mPendingVibratorOffDeadline = 0;
}
return mVibratorOnResult;
}
protected void stopVibrating() {
if (VibrationThread.DEBUG) {
Slog.d(VibrationThread.TAG,
"Turning off vibrator " + getVibratorId());
}
controller.off();
getVibration().stats.reportVibratorOff();
mPendingVibratorOffDeadline = 0;
}
protected void changeAmplitude(float amplitude) {
if (VibrationThread.DEBUG) {
Slog.d(VibrationThread.TAG,
"Amplitude changed on vibrator " + getVibratorId() + " to " + amplitude);
}
controller.setAmplitude(amplitude);
getVibration().stats.reportSetAmplitude();
}
/**
* 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.
*/
protected List<Step> nextSteps(int segmentsPlayed) {
// Schedule next steps to run right away.
long nextStartTime = SystemClock.uptimeMillis();
if (mVibratorOnResult > 0) {
// Vibrator was turned on by this step, with mVibratorOnResult as the duration.
// Schedule next steps for right after the vibration finishes.
nextStartTime += mVibratorOnResult;
}
return nextSteps(nextStartTime, segmentsPlayed);
}
/**
* Return the {@link VibrationStepConductor#nextVibrateStep} with given start time,
* which might be calculated independently, and jumping all played segments from the effect.
*
* <p>This should be used when the vibrator on/off state is not responsible for the step
* execution timing, e.g. while playing the vibrator amplitudes.
*/
protected List<Step> nextSteps(long nextStartTime, int segmentsPlayed) {
int nextSegmentIndex = segmentIndex + segmentsPlayed;
int effectSize = effect.getSegments().size();
int repeatIndex = effect.getRepeatIndex();
if (nextSegmentIndex >= effectSize && repeatIndex >= 0) {
// Count the loops that were played.
int loopSize = effectSize - repeatIndex;
int loopSegmentsPlayed = nextSegmentIndex - repeatIndex;
getVibration().stats.reportRepetition(loopSegmentsPlayed / loopSize);
nextSegmentIndex = repeatIndex + ((nextSegmentIndex - effectSize) % loopSize);
}
Step nextStep = conductor.nextVibrateStep(nextStartTime, controller, effect,
nextSegmentIndex, mPendingVibratorOffDeadline);
return nextStep == null ? VibrationStepConductor.EMPTY_STEP_LIST : Arrays.asList(nextStep);
}
}