Merge "Extract Steps and StepQueue out of VibrationThread." into tm-dev
This commit is contained in:
@@ -0,0 +1,148 @@
|
|||||||
|
/*
|
||||||
|
* 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;
|
||||||
|
public final long previousStepVibratorOffTimeout;
|
||||||
|
|
||||||
|
long mVibratorOnResult;
|
||||||
|
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 previousStepVibratorOffTimeout 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 previousStepVibratorOffTimeout) {
|
||||||
|
super(conductor, startTime);
|
||||||
|
this.controller = controller;
|
||||||
|
this.effect = effect;
|
||||||
|
this.segmentIndex = index;
|
||||||
|
this.previousStepVibratorOffTimeout = previousStepVibratorOffTimeout;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getVibratorId() {
|
||||||
|
return controller.getVibratorInfo().getId();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getVibratorOnDuration() {
|
||||||
|
return mVibratorOnResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean acceptVibratorCompleteCallback(int vibratorId) {
|
||||||
|
boolean isSameVibrator = controller.getVibratorInfo().getId() == vibratorId;
|
||||||
|
mVibratorCompleteCallbackReceived |= isSameVibrator;
|
||||||
|
// 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.
|
||||||
|
return isSameVibrator && (previousStepVibratorOffTimeout > SystemClock.uptimeMillis());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<Step> cancel() {
|
||||||
|
return Arrays.asList(new CompleteEffectVibratorStep(conductor, SystemClock.uptimeMillis(),
|
||||||
|
/* cancelled= */ true, controller, previousStepVibratorOffTimeout));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void cancelImmediately() {
|
||||||
|
if (previousStepVibratorOffTimeout > SystemClock.uptimeMillis()) {
|
||||||
|
// Vibrator might be running from previous steps, so turn it off while canceling.
|
||||||
|
stopVibrating();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void stopVibrating() {
|
||||||
|
if (VibrationThread.DEBUG) {
|
||||||
|
Slog.d(VibrationThread.TAG,
|
||||||
|
"Turning off vibrator " + getVibratorId());
|
||||||
|
}
|
||||||
|
controller.off();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void changeAmplitude(float amplitude) {
|
||||||
|
if (VibrationThread.DEBUG) {
|
||||||
|
Slog.d(VibrationThread.TAG,
|
||||||
|
"Amplitude changed on vibrator " + getVibratorId() + " to " + amplitude);
|
||||||
|
}
|
||||||
|
controller.setAmplitude(amplitude);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the {@link VibrationStepConductor#nextVibrateStep} with same timings, only jumping
|
||||||
|
* the segments.
|
||||||
|
*/
|
||||||
|
protected List<Step> skipToNextSteps(int segmentsSkipped) {
|
||||||
|
return nextSteps(startTime, previousStepVibratorOffTimeout, segmentsSkipped);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the {@link VibrationStepConductor#nextVibrateStep} with same start and off timings
|
||||||
|
* calculated from {@link #getVibratorOnDuration()}, jumping all played segments.
|
||||||
|
*
|
||||||
|
* <p>This method has same behavior as {@link #skipToNextSteps(int)} when the vibrator
|
||||||
|
* result is non-positive, meaning the vibrator has either ignored or failed to turn on.
|
||||||
|
*/
|
||||||
|
protected List<Step> nextSteps(int segmentsPlayed) {
|
||||||
|
if (mVibratorOnResult <= 0) {
|
||||||
|
// Vibration was not started, so just skip the played segments and keep timings.
|
||||||
|
return skipToNextSteps(segmentsPlayed);
|
||||||
|
}
|
||||||
|
long nextStartTime = SystemClock.uptimeMillis() + mVibratorOnResult;
|
||||||
|
long nextVibratorOffTimeout =
|
||||||
|
nextStartTime + VibrationStepConductor.CALLBACKS_EXTRA_TIMEOUT;
|
||||||
|
return nextSteps(nextStartTime, nextVibratorOffTimeout, segmentsPlayed);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the {@link VibrationStepConductor#nextVibrateStep} with given start and off timings,
|
||||||
|
* which might be calculated independently, jumping all played segments.
|
||||||
|
*
|
||||||
|
* <p>This should be used when the vibrator on/off state is not responsible for the steps
|
||||||
|
* execution timings, e.g. while playing the vibrator amplitudes.
|
||||||
|
*/
|
||||||
|
protected List<Step> nextSteps(long nextStartTime, long vibratorOffTimeout,
|
||||||
|
int segmentsPlayed) {
|
||||||
|
Step nextStep = conductor.nextVibrateStep(nextStartTime, controller, effect,
|
||||||
|
segmentIndex + segmentsPlayed, vibratorOffTimeout);
|
||||||
|
return nextStep == null ? VibrationStepConductor.EMPTY_STEP_LIST : Arrays.asList(nextStep);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,114 @@
|
|||||||
|
/*
|
||||||
|
* 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.Trace;
|
||||||
|
import android.os.VibrationEffect;
|
||||||
|
import android.util.Slog;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents a step to complete a {@link VibrationEffect}.
|
||||||
|
*
|
||||||
|
* <p>This runs right at the time the vibration is considered to end and will update the pending
|
||||||
|
* vibrators count. This can turn off the vibrator or slowly ramp it down to zero amplitude.
|
||||||
|
*/
|
||||||
|
final class CompleteEffectVibratorStep extends AbstractVibratorStep {
|
||||||
|
private final boolean mCancelled;
|
||||||
|
|
||||||
|
CompleteEffectVibratorStep(VibrationStepConductor conductor, long startTime, boolean cancelled,
|
||||||
|
VibratorController controller, long previousStepVibratorOffTimeout) {
|
||||||
|
super(conductor, startTime, controller, /* effect= */ null, /* index= */ -1,
|
||||||
|
previousStepVibratorOffTimeout);
|
||||||
|
mCancelled = cancelled;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isCleanUp() {
|
||||||
|
// If the vibration was cancelled then this is just a clean up to ramp off the vibrator.
|
||||||
|
// Otherwise this step is part of the vibration.
|
||||||
|
return mCancelled;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<Step> cancel() {
|
||||||
|
if (mCancelled) {
|
||||||
|
// Double cancelling will just turn off the vibrator right away.
|
||||||
|
return Arrays.asList(
|
||||||
|
new TurnOffVibratorStep(conductor, SystemClock.uptimeMillis(), controller));
|
||||||
|
}
|
||||||
|
return super.cancel();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<Step> play() {
|
||||||
|
Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "CompleteEffectVibratorStep");
|
||||||
|
try {
|
||||||
|
if (VibrationThread.DEBUG) {
|
||||||
|
Slog.d(VibrationThread.TAG,
|
||||||
|
"Running " + (mCancelled ? "cancel" : "complete") + " vibration"
|
||||||
|
+ " step on vibrator " + controller.getVibratorInfo().getId());
|
||||||
|
}
|
||||||
|
if (mVibratorCompleteCallbackReceived) {
|
||||||
|
// Vibration completion callback was received by this step, just turn if off
|
||||||
|
// and skip any clean-up.
|
||||||
|
stopVibrating();
|
||||||
|
return VibrationStepConductor.EMPTY_STEP_LIST;
|
||||||
|
}
|
||||||
|
|
||||||
|
float currentAmplitude = controller.getCurrentAmplitude();
|
||||||
|
long remainingOnDuration =
|
||||||
|
previousStepVibratorOffTimeout - VibrationStepConductor.CALLBACKS_EXTRA_TIMEOUT
|
||||||
|
- SystemClock.uptimeMillis();
|
||||||
|
long rampDownDuration =
|
||||||
|
Math.min(remainingOnDuration,
|
||||||
|
conductor.vibrationSettings.getRampDownDuration());
|
||||||
|
long stepDownDuration = conductor.vibrationSettings.getRampStepDuration();
|
||||||
|
if (currentAmplitude < VibrationStepConductor.RAMP_OFF_AMPLITUDE_MIN
|
||||||
|
|| rampDownDuration <= stepDownDuration) {
|
||||||
|
// No need to ramp down the amplitude, just wait to turn it off.
|
||||||
|
if (mCancelled) {
|
||||||
|
// Vibration is completing because it was cancelled, turn off right away.
|
||||||
|
stopVibrating();
|
||||||
|
return VibrationStepConductor.EMPTY_STEP_LIST;
|
||||||
|
} else {
|
||||||
|
return Arrays.asList(new TurnOffVibratorStep(
|
||||||
|
conductor, previousStepVibratorOffTimeout, controller));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (VibrationThread.DEBUG) {
|
||||||
|
Slog.d(VibrationThread.TAG,
|
||||||
|
"Ramping down vibrator " + controller.getVibratorInfo().getId()
|
||||||
|
+ " from amplitude " + currentAmplitude
|
||||||
|
+ " for " + rampDownDuration + "ms");
|
||||||
|
}
|
||||||
|
float amplitudeDelta = currentAmplitude / (rampDownDuration / stepDownDuration);
|
||||||
|
float amplitudeTarget = currentAmplitude - amplitudeDelta;
|
||||||
|
long newVibratorOffTimeout =
|
||||||
|
mCancelled ? rampDownDuration : previousStepVibratorOffTimeout;
|
||||||
|
return Arrays.asList(
|
||||||
|
new RampOffVibratorStep(conductor, startTime, amplitudeTarget, amplitudeDelta,
|
||||||
|
controller, newVibratorOffTimeout));
|
||||||
|
} finally {
|
||||||
|
Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,84 @@
|
|||||||
|
/*
|
||||||
|
* 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.Trace;
|
||||||
|
import android.os.VibrationEffect;
|
||||||
|
import android.os.vibrator.PrimitiveSegment;
|
||||||
|
import android.os.vibrator.VibrationEffectSegment;
|
||||||
|
import android.util.Slog;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents a step to turn the vibrator on using a composition of primitives.
|
||||||
|
*
|
||||||
|
* <p>This step will use the maximum supported number of consecutive segments of type
|
||||||
|
* {@link PrimitiveSegment} starting at the current index.
|
||||||
|
*/
|
||||||
|
final class ComposePrimitivesVibratorStep extends AbstractVibratorStep {
|
||||||
|
|
||||||
|
ComposePrimitivesVibratorStep(VibrationStepConductor conductor, long startTime,
|
||||||
|
VibratorController controller, VibrationEffect.Composed effect, int index,
|
||||||
|
long previousStepVibratorOffTimeout) {
|
||||||
|
// This step should wait for the last vibration to finish (with the timeout) and for the
|
||||||
|
// intended step start time (to respect the effect delays).
|
||||||
|
super(conductor, Math.max(startTime, previousStepVibratorOffTimeout), controller, effect,
|
||||||
|
index, previousStepVibratorOffTimeout);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<Step> play() {
|
||||||
|
Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "ComposePrimitivesStep");
|
||||||
|
try {
|
||||||
|
// Load the next PrimitiveSegments to create a single compose call to the vibrator,
|
||||||
|
// limited to the vibrator composition maximum size.
|
||||||
|
int limit = controller.getVibratorInfo().getCompositionSizeMax();
|
||||||
|
int segmentCount = limit > 0
|
||||||
|
? Math.min(effect.getSegments().size(), segmentIndex + limit)
|
||||||
|
: effect.getSegments().size();
|
||||||
|
List<PrimitiveSegment> primitives = new ArrayList<>();
|
||||||
|
for (int i = segmentIndex; i < segmentCount; i++) {
|
||||||
|
VibrationEffectSegment segment = effect.getSegments().get(i);
|
||||||
|
if (segment instanceof PrimitiveSegment) {
|
||||||
|
primitives.add((PrimitiveSegment) segment);
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (primitives.isEmpty()) {
|
||||||
|
Slog.w(VibrationThread.TAG, "Ignoring wrong segment for a ComposePrimitivesStep: "
|
||||||
|
+ effect.getSegments().get(segmentIndex));
|
||||||
|
return skipToNextSteps(/* segmentsSkipped= */ 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (VibrationThread.DEBUG) {
|
||||||
|
Slog.d(VibrationThread.TAG, "Compose " + primitives + " primitives on vibrator "
|
||||||
|
+ controller.getVibratorInfo().getId());
|
||||||
|
}
|
||||||
|
mVibratorOnResult = controller.on(
|
||||||
|
primitives.toArray(new PrimitiveSegment[primitives.size()]),
|
||||||
|
getVibration().id);
|
||||||
|
|
||||||
|
return nextSteps(/* segmentsPlayed= */ primitives.size());
|
||||||
|
} finally {
|
||||||
|
Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,84 @@
|
|||||||
|
/*
|
||||||
|
* 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.Trace;
|
||||||
|
import android.os.VibrationEffect;
|
||||||
|
import android.os.vibrator.RampSegment;
|
||||||
|
import android.os.vibrator.StepSegment;
|
||||||
|
import android.os.vibrator.VibrationEffectSegment;
|
||||||
|
import android.util.Slog;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents a step to turn the vibrator on using a composition of PWLE segments.
|
||||||
|
*
|
||||||
|
* <p>This step will use the maximum supported number of consecutive segments of type
|
||||||
|
* {@link StepSegment} or {@link RampSegment} starting at the current index.
|
||||||
|
*/
|
||||||
|
final class ComposePwleVibratorStep extends AbstractVibratorStep {
|
||||||
|
|
||||||
|
ComposePwleVibratorStep(VibrationStepConductor conductor, long startTime,
|
||||||
|
VibratorController controller, VibrationEffect.Composed effect, int index,
|
||||||
|
long previousStepVibratorOffTimeout) {
|
||||||
|
// This step should wait for the last vibration to finish (with the timeout) and for the
|
||||||
|
// intended step start time (to respect the effect delays).
|
||||||
|
super(conductor, Math.max(startTime, previousStepVibratorOffTimeout), controller, effect,
|
||||||
|
index, previousStepVibratorOffTimeout);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<Step> play() {
|
||||||
|
Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "ComposePwleStep");
|
||||||
|
try {
|
||||||
|
// Load the next RampSegments to create a single composePwle call to the vibrator,
|
||||||
|
// limited to the vibrator PWLE maximum size.
|
||||||
|
int limit = controller.getVibratorInfo().getPwleSizeMax();
|
||||||
|
int segmentCount = limit > 0
|
||||||
|
? Math.min(effect.getSegments().size(), segmentIndex + limit)
|
||||||
|
: effect.getSegments().size();
|
||||||
|
List<RampSegment> pwles = new ArrayList<>();
|
||||||
|
for (int i = segmentIndex; i < segmentCount; i++) {
|
||||||
|
VibrationEffectSegment segment = effect.getSegments().get(i);
|
||||||
|
if (segment instanceof RampSegment) {
|
||||||
|
pwles.add((RampSegment) segment);
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pwles.isEmpty()) {
|
||||||
|
Slog.w(VibrationThread.TAG, "Ignoring wrong segment for a ComposePwleStep: "
|
||||||
|
+ effect.getSegments().get(segmentIndex));
|
||||||
|
return skipToNextSteps(/* segmentsSkipped= */ 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (VibrationThread.DEBUG) {
|
||||||
|
Slog.d(VibrationThread.TAG, "Compose " + pwles + " PWLEs on vibrator "
|
||||||
|
+ controller.getVibratorInfo().getId());
|
||||||
|
}
|
||||||
|
mVibratorOnResult = controller.on(pwles.toArray(new RampSegment[pwles.size()]),
|
||||||
|
getVibration().id);
|
||||||
|
|
||||||
|
return nextSteps(/* segmentsPlayed= */ pwles.size());
|
||||||
|
} finally {
|
||||||
|
Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,73 @@
|
|||||||
|
/*
|
||||||
|
* 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.Trace;
|
||||||
|
import android.util.Slog;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Finish a sync vibration started by a {@link StartSequentialEffectStep}.
|
||||||
|
*
|
||||||
|
* <p>This only plays after all active vibrators steps have finished, and adds a {@link
|
||||||
|
* StartSequentialEffectStep} to the queue if the sequential effect isn't finished yet.
|
||||||
|
*/
|
||||||
|
final class FinishSequentialEffectStep extends Step {
|
||||||
|
public final StartSequentialEffectStep startedStep;
|
||||||
|
|
||||||
|
FinishSequentialEffectStep(StartSequentialEffectStep startedStep) {
|
||||||
|
// No predefined startTime, just wait for all steps in the queue.
|
||||||
|
super(startedStep.conductor, Long.MAX_VALUE);
|
||||||
|
this.startedStep = startedStep;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isCleanUp() {
|
||||||
|
// This step only notes that all the vibrators has been turned off.
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<Step> play() {
|
||||||
|
Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "FinishSequentialEffectStep");
|
||||||
|
try {
|
||||||
|
if (VibrationThread.DEBUG) {
|
||||||
|
Slog.d(VibrationThread.TAG,
|
||||||
|
"FinishSequentialEffectStep for effect #" + startedStep.currentIndex);
|
||||||
|
}
|
||||||
|
conductor.vibratorManagerHooks.noteVibratorOff(conductor.getVibration().uid);
|
||||||
|
Step nextStep = startedStep.nextStep();
|
||||||
|
return nextStep == null ? VibrationStepConductor.EMPTY_STEP_LIST
|
||||||
|
: Arrays.asList(nextStep);
|
||||||
|
} finally {
|
||||||
|
Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<Step> cancel() {
|
||||||
|
cancelImmediately();
|
||||||
|
return VibrationStepConductor.EMPTY_STEP_LIST;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void cancelImmediately() {
|
||||||
|
conductor.vibratorManagerHooks.noteVibratorOff(conductor.getVibration().uid);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,103 @@
|
|||||||
|
/*
|
||||||
|
* 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.Trace;
|
||||||
|
import android.os.VibrationEffect;
|
||||||
|
import android.os.vibrator.PrebakedSegment;
|
||||||
|
import android.os.vibrator.VibrationEffectSegment;
|
||||||
|
import android.util.Slog;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents a step to turn the vibrator on with a single prebaked effect.
|
||||||
|
*
|
||||||
|
* <p>This step automatically falls back by replacing the prebaked segment with
|
||||||
|
* {@link VibrationSettings#getFallbackEffect(int)}, if available.
|
||||||
|
*/
|
||||||
|
final class PerformPrebakedVibratorStep extends AbstractVibratorStep {
|
||||||
|
|
||||||
|
PerformPrebakedVibratorStep(VibrationStepConductor conductor, long startTime,
|
||||||
|
VibratorController controller, VibrationEffect.Composed effect, int index,
|
||||||
|
long previousStepVibratorOffTimeout) {
|
||||||
|
// This step should wait for the last vibration to finish (with the timeout) and for the
|
||||||
|
// intended step start time (to respect the effect delays).
|
||||||
|
super(conductor, Math.max(startTime, previousStepVibratorOffTimeout), controller, effect,
|
||||||
|
index, previousStepVibratorOffTimeout);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<Step> play() {
|
||||||
|
Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "PerformPrebakedVibratorStep");
|
||||||
|
try {
|
||||||
|
VibrationEffectSegment segment = effect.getSegments().get(segmentIndex);
|
||||||
|
if (!(segment instanceof PrebakedSegment)) {
|
||||||
|
Slog.w(VibrationThread.TAG, "Ignoring wrong segment for a "
|
||||||
|
+ "PerformPrebakedVibratorStep: " + segment);
|
||||||
|
return skipToNextSteps(/* segmentsSkipped= */ 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
PrebakedSegment prebaked = (PrebakedSegment) segment;
|
||||||
|
if (VibrationThread.DEBUG) {
|
||||||
|
Slog.d(VibrationThread.TAG, "Perform " + VibrationEffect.effectIdToString(
|
||||||
|
prebaked.getEffectId()) + " on vibrator "
|
||||||
|
+ controller.getVibratorInfo().getId());
|
||||||
|
}
|
||||||
|
|
||||||
|
VibrationEffect fallback = getVibration().getFallback(prebaked.getEffectId());
|
||||||
|
mVibratorOnResult = controller.on(prebaked, getVibration().id);
|
||||||
|
|
||||||
|
if (mVibratorOnResult == 0 && prebaked.shouldFallback()
|
||||||
|
&& (fallback instanceof VibrationEffect.Composed)) {
|
||||||
|
if (VibrationThread.DEBUG) {
|
||||||
|
Slog.d(VibrationThread.TAG, "Playing fallback for effect "
|
||||||
|
+ VibrationEffect.effectIdToString(prebaked.getEffectId()));
|
||||||
|
}
|
||||||
|
AbstractVibratorStep fallbackStep = conductor.nextVibrateStep(startTime, controller,
|
||||||
|
replaceCurrentSegment((VibrationEffect.Composed) fallback),
|
||||||
|
segmentIndex, previousStepVibratorOffTimeout);
|
||||||
|
List<Step> fallbackResult = fallbackStep.play();
|
||||||
|
// Update the result with the fallback result so this step is seamlessly
|
||||||
|
// replaced by the fallback to any outer application of this.
|
||||||
|
mVibratorOnResult = fallbackStep.getVibratorOnDuration();
|
||||||
|
return fallbackResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
return nextSteps(/* segmentsPlayed= */ 1);
|
||||||
|
} finally {
|
||||||
|
Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Replace segment at {@link #segmentIndex} in {@link #effect} with given fallback segments.
|
||||||
|
*
|
||||||
|
* @return a copy of {@link #effect} with replaced segment.
|
||||||
|
*/
|
||||||
|
private VibrationEffect.Composed replaceCurrentSegment(VibrationEffect.Composed fallback) {
|
||||||
|
List<VibrationEffectSegment> newSegments = new ArrayList<>(effect.getSegments());
|
||||||
|
int newRepeatIndex = effect.getRepeatIndex();
|
||||||
|
newSegments.remove(segmentIndex);
|
||||||
|
newSegments.addAll(segmentIndex, fallback.getSegments());
|
||||||
|
if (segmentIndex < effect.getRepeatIndex()) {
|
||||||
|
newRepeatIndex += fallback.getSegments().size() - 1;
|
||||||
|
}
|
||||||
|
return new VibrationEffect.Composed(newSegments, newRepeatIndex);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,84 @@
|
|||||||
|
/*
|
||||||
|
* 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.Trace;
|
||||||
|
import android.util.Slog;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/** Represents a step to ramp down the vibrator amplitude before turning it off. */
|
||||||
|
final class RampOffVibratorStep extends AbstractVibratorStep {
|
||||||
|
private final float mAmplitudeTarget;
|
||||||
|
private final float mAmplitudeDelta;
|
||||||
|
|
||||||
|
RampOffVibratorStep(VibrationStepConductor conductor, long startTime, float amplitudeTarget,
|
||||||
|
float amplitudeDelta, VibratorController controller,
|
||||||
|
long previousStepVibratorOffTimeout) {
|
||||||
|
super(conductor, startTime, controller, /* effect= */ null, /* index= */ -1,
|
||||||
|
previousStepVibratorOffTimeout);
|
||||||
|
mAmplitudeTarget = amplitudeTarget;
|
||||||
|
mAmplitudeDelta = amplitudeDelta;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isCleanUp() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<Step> cancel() {
|
||||||
|
return Arrays.asList(
|
||||||
|
new TurnOffVibratorStep(conductor, SystemClock.uptimeMillis(), controller));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<Step> play() {
|
||||||
|
Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "RampOffVibratorStep");
|
||||||
|
try {
|
||||||
|
if (VibrationThread.DEBUG) {
|
||||||
|
long latency = SystemClock.uptimeMillis() - startTime;
|
||||||
|
Slog.d(VibrationThread.TAG, "Ramp down the vibrator amplitude, step with "
|
||||||
|
+ latency + "ms latency.");
|
||||||
|
}
|
||||||
|
if (mVibratorCompleteCallbackReceived) {
|
||||||
|
// Vibration completion callback was received by this step, just turn if off
|
||||||
|
// and skip the rest of the steps to ramp down the vibrator amplitude.
|
||||||
|
stopVibrating();
|
||||||
|
return VibrationStepConductor.EMPTY_STEP_LIST;
|
||||||
|
}
|
||||||
|
|
||||||
|
changeAmplitude(mAmplitudeTarget);
|
||||||
|
|
||||||
|
float newAmplitudeTarget = mAmplitudeTarget - mAmplitudeDelta;
|
||||||
|
if (newAmplitudeTarget < VibrationStepConductor.RAMP_OFF_AMPLITUDE_MIN) {
|
||||||
|
// Vibrator amplitude cannot go further down, just turn it off.
|
||||||
|
return Arrays.asList(new TurnOffVibratorStep(
|
||||||
|
conductor, previousStepVibratorOffTimeout, controller));
|
||||||
|
}
|
||||||
|
return Arrays.asList(new RampOffVibratorStep(
|
||||||
|
conductor,
|
||||||
|
startTime + conductor.vibrationSettings.getRampStepDuration(),
|
||||||
|
newAmplitudeTarget, mAmplitudeDelta, controller,
|
||||||
|
previousStepVibratorOffTimeout));
|
||||||
|
} finally {
|
||||||
|
Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,186 @@
|
|||||||
|
/*
|
||||||
|
* 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.Trace;
|
||||||
|
import android.os.VibrationEffect;
|
||||||
|
import android.os.vibrator.StepSegment;
|
||||||
|
import android.os.vibrator.VibrationEffectSegment;
|
||||||
|
import android.util.Slog;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents a step to turn the vibrator on and change its amplitude.
|
||||||
|
*
|
||||||
|
* <p>This step ignores vibration completion callbacks and control the vibrator on/off state
|
||||||
|
* and amplitude to simulate waveforms represented by a sequence of {@link StepSegment}.
|
||||||
|
*/
|
||||||
|
final class SetAmplitudeVibratorStep extends AbstractVibratorStep {
|
||||||
|
private long mNextOffTime;
|
||||||
|
|
||||||
|
SetAmplitudeVibratorStep(VibrationStepConductor conductor, long startTime,
|
||||||
|
VibratorController controller, VibrationEffect.Composed effect, int index,
|
||||||
|
long previousStepVibratorOffTimeout) {
|
||||||
|
// This step has a fixed startTime coming from the timings of the waveform it's playing.
|
||||||
|
super(conductor, startTime, controller, effect, index, previousStepVibratorOffTimeout);
|
||||||
|
mNextOffTime = previousStepVibratorOffTimeout;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean acceptVibratorCompleteCallback(int vibratorId) {
|
||||||
|
if (controller.getVibratorInfo().getId() == vibratorId) {
|
||||||
|
mVibratorCompleteCallbackReceived = true;
|
||||||
|
mNextOffTime = SystemClock.uptimeMillis();
|
||||||
|
}
|
||||||
|
// Timings are tightly controlled here, so only trigger this step if the vibrator was
|
||||||
|
// supposed to be ON but has completed prematurely, to turn it back on as soon as
|
||||||
|
// possible.
|
||||||
|
return mNextOffTime < startTime && controller.getCurrentAmplitude() > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<Step> play() {
|
||||||
|
// TODO: consider separating the "on" steps at the start into a separate Step.
|
||||||
|
// TODO: consider instantiating the step with the required amplitude, rather than
|
||||||
|
// needing to dig into the effect.
|
||||||
|
Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "SetAmplitudeVibratorStep");
|
||||||
|
try {
|
||||||
|
long now = SystemClock.uptimeMillis();
|
||||||
|
long latency = now - startTime;
|
||||||
|
if (VibrationThread.DEBUG) {
|
||||||
|
Slog.d(VibrationThread.TAG,
|
||||||
|
"Running amplitude step with " + latency + "ms latency.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mVibratorCompleteCallbackReceived && latency < 0) {
|
||||||
|
// This step was run early because the vibrator turned off prematurely.
|
||||||
|
// Turn it back on and return this same step to run at the exact right time.
|
||||||
|
mNextOffTime = turnVibratorBackOn(/* remainingDuration= */ -latency);
|
||||||
|
return Arrays.asList(new SetAmplitudeVibratorStep(conductor, startTime, controller,
|
||||||
|
effect, segmentIndex, mNextOffTime));
|
||||||
|
}
|
||||||
|
|
||||||
|
VibrationEffectSegment segment = effect.getSegments().get(segmentIndex);
|
||||||
|
if (!(segment instanceof StepSegment)) {
|
||||||
|
Slog.w(VibrationThread.TAG,
|
||||||
|
"Ignoring wrong segment for a SetAmplitudeVibratorStep: " + segment);
|
||||||
|
return skipToNextSteps(/* segmentsSkipped= */ 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
StepSegment stepSegment = (StepSegment) segment;
|
||||||
|
if (stepSegment.getDuration() == 0) {
|
||||||
|
// Skip waveform entries with zero timing.
|
||||||
|
return skipToNextSteps(/* segmentsSkipped= */ 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
float amplitude = stepSegment.getAmplitude();
|
||||||
|
if (amplitude == 0) {
|
||||||
|
if (previousStepVibratorOffTimeout > now) {
|
||||||
|
// Amplitude cannot be set to zero, so stop the vibrator.
|
||||||
|
stopVibrating();
|
||||||
|
mNextOffTime = now;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (startTime >= mNextOffTime) {
|
||||||
|
// Vibrator is OFF. Turn vibrator back on for the duration of another
|
||||||
|
// cycle before setting the amplitude.
|
||||||
|
long onDuration = getVibratorOnDuration(effect, segmentIndex);
|
||||||
|
if (onDuration > 0) {
|
||||||
|
mVibratorOnResult = startVibrating(onDuration);
|
||||||
|
mNextOffTime = now + onDuration
|
||||||
|
+ VibrationStepConductor.CALLBACKS_EXTRA_TIMEOUT;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
changeAmplitude(amplitude);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use original startTime to avoid propagating latencies to the waveform.
|
||||||
|
long nextStartTime = startTime + segment.getDuration();
|
||||||
|
return nextSteps(nextStartTime, mNextOffTime, /* segmentsPlayed= */ 1);
|
||||||
|
} finally {
|
||||||
|
Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private long turnVibratorBackOn(long remainingDuration) {
|
||||||
|
long onDuration = getVibratorOnDuration(effect, segmentIndex);
|
||||||
|
if (onDuration <= 0) {
|
||||||
|
// Vibrator is supposed to go back off when this step starts, so just leave it off.
|
||||||
|
return previousStepVibratorOffTimeout;
|
||||||
|
}
|
||||||
|
onDuration += remainingDuration;
|
||||||
|
float expectedAmplitude = controller.getCurrentAmplitude();
|
||||||
|
mVibratorOnResult = startVibrating(onDuration);
|
||||||
|
if (mVibratorOnResult > 0) {
|
||||||
|
// Set the amplitude back to the value it was supposed to be playing at.
|
||||||
|
changeAmplitude(expectedAmplitude);
|
||||||
|
}
|
||||||
|
return SystemClock.uptimeMillis() + onDuration
|
||||||
|
+ VibrationStepConductor.CALLBACKS_EXTRA_TIMEOUT;
|
||||||
|
}
|
||||||
|
|
||||||
|
private long startVibrating(long duration) {
|
||||||
|
if (VibrationThread.DEBUG) {
|
||||||
|
Slog.d(VibrationThread.TAG,
|
||||||
|
"Turning on vibrator " + controller.getVibratorInfo().getId() + " for "
|
||||||
|
+ duration + "ms");
|
||||||
|
}
|
||||||
|
return controller.on(duration, getVibration().id);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the duration the vibrator will be on for a waveform, starting at {@code startIndex}
|
||||||
|
* until the next time it's vibrating amplitude is zero or a different type of segment is
|
||||||
|
* found.
|
||||||
|
*/
|
||||||
|
private long getVibratorOnDuration(VibrationEffect.Composed effect, int startIndex) {
|
||||||
|
List<VibrationEffectSegment> segments = effect.getSegments();
|
||||||
|
int segmentCount = segments.size();
|
||||||
|
int repeatIndex = effect.getRepeatIndex();
|
||||||
|
int i = startIndex;
|
||||||
|
long timing = 0;
|
||||||
|
while (i < segmentCount) {
|
||||||
|
VibrationEffectSegment segment = segments.get(i);
|
||||||
|
if (!(segment instanceof StepSegment)
|
||||||
|
|| ((StepSegment) segment).getAmplitude() == 0) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
timing += segment.getDuration();
|
||||||
|
i++;
|
||||||
|
if (i == segmentCount && repeatIndex >= 0) {
|
||||||
|
i = repeatIndex;
|
||||||
|
// prevent infinite loop
|
||||||
|
repeatIndex = -1;
|
||||||
|
}
|
||||||
|
if (i == startIndex) {
|
||||||
|
// The repeating waveform keeps the vibrator ON all the time. Use a minimum
|
||||||
|
// of 1s duration to prevent short patterns from turning the vibrator ON too
|
||||||
|
// frequently.
|
||||||
|
return Math.max(timing, 1000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (i == segmentCount && effect.getRepeatIndex() < 0) {
|
||||||
|
// Vibration ending at non-zero amplitude, add extra timings to ramp down after
|
||||||
|
// vibration is complete.
|
||||||
|
timing += conductor.vibrationSettings.getRampDownDuration();
|
||||||
|
}
|
||||||
|
return timing;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,371 @@
|
|||||||
|
/*
|
||||||
|
* 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.annotation.Nullable;
|
||||||
|
import android.hardware.vibrator.IVibratorManager;
|
||||||
|
import android.os.CombinedVibration;
|
||||||
|
import android.os.SystemClock;
|
||||||
|
import android.os.Trace;
|
||||||
|
import android.os.VibrationEffect;
|
||||||
|
import android.os.VibratorInfo;
|
||||||
|
import android.os.vibrator.PrebakedSegment;
|
||||||
|
import android.os.vibrator.PrimitiveSegment;
|
||||||
|
import android.os.vibrator.StepSegment;
|
||||||
|
import android.os.vibrator.VibrationEffectSegment;
|
||||||
|
import android.util.Slog;
|
||||||
|
import android.util.SparseArray;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Starts a sync vibration.
|
||||||
|
*
|
||||||
|
* <p>If this step has successfully started playing a vibration on any vibrator, it will always
|
||||||
|
* add a {@link FinishSequentialEffectStep} to the queue, to be played after all vibrators
|
||||||
|
* have finished all their individual steps.
|
||||||
|
*
|
||||||
|
* <p>If this step does not start any vibrator, it will add a {@link StartSequentialEffectStep} if
|
||||||
|
* the sequential effect isn't finished yet.
|
||||||
|
*
|
||||||
|
* <p>TODO: this step actually does several things: multiple HAL calls to sync the vibrators,
|
||||||
|
* as well as dispatching the underlying vibrator instruction calls (which need to be done before
|
||||||
|
* triggering the synced effects). This role/encapsulation could probably be improved to split up
|
||||||
|
* the grouped HAL calls here, as well as to clarify the role of dispatching VibratorSteps between
|
||||||
|
* this class and the controller.
|
||||||
|
*/
|
||||||
|
final class StartSequentialEffectStep extends Step {
|
||||||
|
public final CombinedVibration.Sequential sequentialEffect;
|
||||||
|
public final int currentIndex;
|
||||||
|
|
||||||
|
private long mVibratorsOnMaxDuration;
|
||||||
|
|
||||||
|
StartSequentialEffectStep(VibrationStepConductor conductor,
|
||||||
|
CombinedVibration.Sequential effect) {
|
||||||
|
this(conductor, SystemClock.uptimeMillis() + effect.getDelays().get(0), effect,
|
||||||
|
/* index= */ 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
StartSequentialEffectStep(VibrationStepConductor conductor, long startTime,
|
||||||
|
CombinedVibration.Sequential effect, int index) {
|
||||||
|
super(conductor, startTime);
|
||||||
|
sequentialEffect = effect;
|
||||||
|
currentIndex = index;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getVibratorOnDuration() {
|
||||||
|
return mVibratorsOnMaxDuration;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<Step> play() {
|
||||||
|
Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "StartSequentialEffectStep");
|
||||||
|
List<Step> nextSteps = new ArrayList<>();
|
||||||
|
mVibratorsOnMaxDuration = -1;
|
||||||
|
try {
|
||||||
|
if (VibrationThread.DEBUG) {
|
||||||
|
Slog.d(VibrationThread.TAG,
|
||||||
|
"StartSequentialEffectStep for effect #" + currentIndex);
|
||||||
|
}
|
||||||
|
CombinedVibration effect = sequentialEffect.getEffects().get(currentIndex);
|
||||||
|
DeviceEffectMap effectMapping = createEffectToVibratorMapping(effect);
|
||||||
|
if (effectMapping == null) {
|
||||||
|
// Unable to map effects to vibrators, ignore this step.
|
||||||
|
return nextSteps;
|
||||||
|
}
|
||||||
|
|
||||||
|
mVibratorsOnMaxDuration = startVibrating(effectMapping, nextSteps);
|
||||||
|
if (mVibratorsOnMaxDuration > 0) {
|
||||||
|
conductor.vibratorManagerHooks.noteVibratorOn(conductor.getVibration().uid,
|
||||||
|
mVibratorsOnMaxDuration);
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
if (mVibratorsOnMaxDuration >= 0) {
|
||||||
|
// It least one vibrator was started then add a finish step to wait for all
|
||||||
|
// active vibrators to finish their individual steps before going to the next.
|
||||||
|
// Otherwise this step was ignored so just go to the next one.
|
||||||
|
Step nextStep =
|
||||||
|
mVibratorsOnMaxDuration > 0 ? new FinishSequentialEffectStep(this)
|
||||||
|
: nextStep();
|
||||||
|
if (nextStep != null) {
|
||||||
|
nextSteps.add(nextStep);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
|
||||||
|
}
|
||||||
|
return nextSteps;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<Step> cancel() {
|
||||||
|
return VibrationStepConductor.EMPTY_STEP_LIST;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void cancelImmediately() {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create the next {@link StartSequentialEffectStep} to play this sequential effect, starting at
|
||||||
|
* the
|
||||||
|
* time this method is called, or null if sequence is complete.
|
||||||
|
*/
|
||||||
|
@Nullable
|
||||||
|
Step nextStep() {
|
||||||
|
int nextIndex = currentIndex + 1;
|
||||||
|
if (nextIndex >= sequentialEffect.getEffects().size()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
long nextEffectDelay = sequentialEffect.getDelays().get(nextIndex);
|
||||||
|
long nextStartTime = SystemClock.uptimeMillis() + nextEffectDelay;
|
||||||
|
return new StartSequentialEffectStep(conductor, nextStartTime, sequentialEffect,
|
||||||
|
nextIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Create a mapping of individual {@link VibrationEffect} to available vibrators. */
|
||||||
|
@Nullable
|
||||||
|
private DeviceEffectMap createEffectToVibratorMapping(
|
||||||
|
CombinedVibration effect) {
|
||||||
|
if (effect instanceof CombinedVibration.Mono) {
|
||||||
|
return new DeviceEffectMap((CombinedVibration.Mono) effect);
|
||||||
|
}
|
||||||
|
if (effect instanceof CombinedVibration.Stereo) {
|
||||||
|
return new DeviceEffectMap((CombinedVibration.Stereo) effect);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Starts playing effects on designated vibrators, in sync.
|
||||||
|
*
|
||||||
|
* @param effectMapping The {@link CombinedVibration} mapped to this device vibrators
|
||||||
|
* @param nextSteps An output list to accumulate the future {@link Step
|
||||||
|
* Steps} created
|
||||||
|
* by this method, typically one for each vibrator that has
|
||||||
|
* successfully started vibrating on this step.
|
||||||
|
* @return The duration, in millis, of the {@link CombinedVibration}. Repeating
|
||||||
|
* waveforms return {@link Long#MAX_VALUE}. Zero or negative values indicate the vibrators
|
||||||
|
* have ignored all effects.
|
||||||
|
*/
|
||||||
|
private long startVibrating(
|
||||||
|
DeviceEffectMap effectMapping, List<Step> nextSteps) {
|
||||||
|
int vibratorCount = effectMapping.size();
|
||||||
|
if (vibratorCount == 0) {
|
||||||
|
// No effect was mapped to any available vibrator.
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
AbstractVibratorStep[] steps = new AbstractVibratorStep[vibratorCount];
|
||||||
|
long vibrationStartTime = SystemClock.uptimeMillis();
|
||||||
|
for (int i = 0; i < vibratorCount; i++) {
|
||||||
|
steps[i] = conductor.nextVibrateStep(vibrationStartTime,
|
||||||
|
conductor.getVibrators().get(effectMapping.vibratorIdAt(i)),
|
||||||
|
effectMapping.effectAt(i),
|
||||||
|
/* segmentIndex= */ 0, /* vibratorOffTimeout= */ 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (steps.length == 1) {
|
||||||
|
// No need to prepare and trigger sync effects on a single vibrator.
|
||||||
|
return startVibrating(steps[0], nextSteps);
|
||||||
|
}
|
||||||
|
|
||||||
|
// This synchronization of vibrators should be executed one at a time, even if we are
|
||||||
|
// vibrating different sets of vibrators in parallel. The manager can only prepareSynced
|
||||||
|
// one set of vibrators at a time.
|
||||||
|
// This property is guaranteed by there only being one thread (VibrationThread) executing
|
||||||
|
// one Step at a time, so there's no need to hold the state lock.
|
||||||
|
// TODO: remove the large locked block in a dedicated change.
|
||||||
|
synchronized (conductor.mLock) {
|
||||||
|
boolean hasPrepared = false;
|
||||||
|
boolean hasTriggered = false;
|
||||||
|
long maxDuration = 0;
|
||||||
|
try {
|
||||||
|
hasPrepared = conductor.vibratorManagerHooks.prepareSyncedVibration(
|
||||||
|
effectMapping.getRequiredSyncCapabilities(),
|
||||||
|
effectMapping.getVibratorIds());
|
||||||
|
|
||||||
|
for (AbstractVibratorStep step : steps) {
|
||||||
|
long duration = startVibrating(step, nextSteps);
|
||||||
|
if (duration < 0) {
|
||||||
|
// One vibrator has failed, fail this entire sync attempt.
|
||||||
|
return maxDuration = -1;
|
||||||
|
}
|
||||||
|
maxDuration = Math.max(maxDuration, duration);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if sync was prepared and if any step was accepted by a vibrator,
|
||||||
|
// otherwise there is nothing to trigger here.
|
||||||
|
if (hasPrepared && maxDuration > 0) {
|
||||||
|
hasTriggered = conductor.vibratorManagerHooks.triggerSyncedVibration(
|
||||||
|
getVibration().id);
|
||||||
|
}
|
||||||
|
return maxDuration;
|
||||||
|
} finally {
|
||||||
|
if (hasPrepared && !hasTriggered) {
|
||||||
|
// Trigger has failed or all steps were ignored by the vibrators.
|
||||||
|
conductor.vibratorManagerHooks.cancelSyncedVibration();
|
||||||
|
nextSteps.clear();
|
||||||
|
} else if (maxDuration < 0) {
|
||||||
|
// Some vibrator failed without being prepared so other vibrators might be
|
||||||
|
// active. Cancel and remove every pending step from output list.
|
||||||
|
for (int i = nextSteps.size() - 1; i >= 0; i--) {
|
||||||
|
nextSteps.remove(i).cancelImmediately();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private long startVibrating(AbstractVibratorStep step, List<Step> nextSteps) {
|
||||||
|
nextSteps.addAll(step.play());
|
||||||
|
long stepDuration = step.getVibratorOnDuration();
|
||||||
|
if (stepDuration < 0) {
|
||||||
|
// Step failed, so return negative duration to propagate failure.
|
||||||
|
return stepDuration;
|
||||||
|
}
|
||||||
|
// Return the longest estimation for the entire effect.
|
||||||
|
return Math.max(stepDuration, step.effect.getDuration());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Map a {@link CombinedVibration} to the vibrators available on the device.
|
||||||
|
*
|
||||||
|
* <p>This contains the logic to find the capabilities required from {@link IVibratorManager} to
|
||||||
|
* play all of the effects in sync.
|
||||||
|
*/
|
||||||
|
final class DeviceEffectMap {
|
||||||
|
private final SparseArray<VibrationEffect.Composed> mVibratorEffects;
|
||||||
|
private final int[] mVibratorIds;
|
||||||
|
private final long mRequiredSyncCapabilities;
|
||||||
|
|
||||||
|
DeviceEffectMap(CombinedVibration.Mono mono) {
|
||||||
|
SparseArray<VibratorController> vibrators = conductor.getVibrators();
|
||||||
|
mVibratorEffects = new SparseArray<>(vibrators.size());
|
||||||
|
mVibratorIds = new int[vibrators.size()];
|
||||||
|
for (int i = 0; i < vibrators.size(); i++) {
|
||||||
|
int vibratorId = vibrators.keyAt(i);
|
||||||
|
VibratorInfo vibratorInfo = vibrators.valueAt(i).getVibratorInfo();
|
||||||
|
VibrationEffect effect = conductor.deviceEffectAdapter.apply(
|
||||||
|
mono.getEffect(), vibratorInfo);
|
||||||
|
if (effect instanceof VibrationEffect.Composed) {
|
||||||
|
mVibratorEffects.put(vibratorId, (VibrationEffect.Composed) effect);
|
||||||
|
mVibratorIds[i] = vibratorId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mRequiredSyncCapabilities = calculateRequiredSyncCapabilities(mVibratorEffects);
|
||||||
|
}
|
||||||
|
|
||||||
|
DeviceEffectMap(CombinedVibration.Stereo stereo) {
|
||||||
|
SparseArray<VibratorController> vibrators = conductor.getVibrators();
|
||||||
|
SparseArray<VibrationEffect> stereoEffects = stereo.getEffects();
|
||||||
|
mVibratorEffects = new SparseArray<>();
|
||||||
|
for (int i = 0; i < stereoEffects.size(); i++) {
|
||||||
|
int vibratorId = stereoEffects.keyAt(i);
|
||||||
|
if (vibrators.contains(vibratorId)) {
|
||||||
|
VibratorInfo vibratorInfo = vibrators.valueAt(i).getVibratorInfo();
|
||||||
|
VibrationEffect effect = conductor.deviceEffectAdapter.apply(
|
||||||
|
stereoEffects.valueAt(i), vibratorInfo);
|
||||||
|
if (effect instanceof VibrationEffect.Composed) {
|
||||||
|
mVibratorEffects.put(vibratorId, (VibrationEffect.Composed) effect);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mVibratorIds = new int[mVibratorEffects.size()];
|
||||||
|
for (int i = 0; i < mVibratorEffects.size(); i++) {
|
||||||
|
mVibratorIds[i] = mVibratorEffects.keyAt(i);
|
||||||
|
}
|
||||||
|
mRequiredSyncCapabilities = calculateRequiredSyncCapabilities(mVibratorEffects);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the number of vibrators mapped to play the {@link CombinedVibration} on this
|
||||||
|
* device.
|
||||||
|
*/
|
||||||
|
public int size() {
|
||||||
|
return mVibratorIds.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return all capabilities required to play the {@link CombinedVibration} in
|
||||||
|
* between calls to {@link IVibratorManager#prepareSynced} and
|
||||||
|
* {@link IVibratorManager#triggerSynced}.
|
||||||
|
*/
|
||||||
|
public long getRequiredSyncCapabilities() {
|
||||||
|
return mRequiredSyncCapabilities;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Return all vibrator ids mapped to play the {@link CombinedVibration}. */
|
||||||
|
public int[] getVibratorIds() {
|
||||||
|
return mVibratorIds;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Return the id of the vibrator at given index. */
|
||||||
|
public int vibratorIdAt(int index) {
|
||||||
|
return mVibratorEffects.keyAt(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Return the {@link VibrationEffect} at given index. */
|
||||||
|
public VibrationEffect.Composed effectAt(int index) {
|
||||||
|
return mVibratorEffects.valueAt(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return all capabilities required from the {@link IVibratorManager} to prepare and
|
||||||
|
* trigger all given effects in sync.
|
||||||
|
*
|
||||||
|
* @return {@link IVibratorManager#CAP_SYNC} together with all required
|
||||||
|
* IVibratorManager.CAP_PREPARE_* and IVibratorManager.CAP_MIXED_TRIGGER_* capabilities.
|
||||||
|
*/
|
||||||
|
private long calculateRequiredSyncCapabilities(
|
||||||
|
SparseArray<VibrationEffect.Composed> effects) {
|
||||||
|
long prepareCap = 0;
|
||||||
|
for (int i = 0; i < effects.size(); i++) {
|
||||||
|
VibrationEffectSegment firstSegment = effects.valueAt(i).getSegments().get(0);
|
||||||
|
if (firstSegment instanceof StepSegment) {
|
||||||
|
prepareCap |= IVibratorManager.CAP_PREPARE_ON;
|
||||||
|
} else if (firstSegment instanceof PrebakedSegment) {
|
||||||
|
prepareCap |= IVibratorManager.CAP_PREPARE_PERFORM;
|
||||||
|
} else if (firstSegment instanceof PrimitiveSegment) {
|
||||||
|
prepareCap |= IVibratorManager.CAP_PREPARE_COMPOSE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
int triggerCap = 0;
|
||||||
|
if (requireMixedTriggerCapability(prepareCap, IVibratorManager.CAP_PREPARE_ON)) {
|
||||||
|
triggerCap |= IVibratorManager.CAP_MIXED_TRIGGER_ON;
|
||||||
|
}
|
||||||
|
if (requireMixedTriggerCapability(prepareCap, IVibratorManager.CAP_PREPARE_PERFORM)) {
|
||||||
|
triggerCap |= IVibratorManager.CAP_MIXED_TRIGGER_PERFORM;
|
||||||
|
}
|
||||||
|
if (requireMixedTriggerCapability(prepareCap, IVibratorManager.CAP_PREPARE_COMPOSE)) {
|
||||||
|
triggerCap |= IVibratorManager.CAP_MIXED_TRIGGER_COMPOSE;
|
||||||
|
}
|
||||||
|
return IVibratorManager.CAP_SYNC | prepareCap | triggerCap;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return true if {@code prepareCapabilities} contains this {@code capability} mixed with
|
||||||
|
* different ones, requiring a mixed trigger capability from the vibrator manager for
|
||||||
|
* syncing all effects.
|
||||||
|
*/
|
||||||
|
private boolean requireMixedTriggerCapability(long prepareCapabilities, long capability) {
|
||||||
|
return (prepareCapabilities & capability) != 0
|
||||||
|
&& (prepareCapabilities & ~capability) != 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
103
services/core/java/com/android/server/vibrator/Step.java
Normal file
103
services/core/java/com/android/server/vibrator/Step.java
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
/*
|
||||||
|
* 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.annotation.NonNull;
|
||||||
|
import android.os.CombinedVibration;
|
||||||
|
import android.os.SystemClock;
|
||||||
|
import android.os.VibrationEffect;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represent a single step for playing a vibration.
|
||||||
|
*
|
||||||
|
* <p>Every step has a start time, which can be used to apply delays between steps while
|
||||||
|
* executing them in sequence.
|
||||||
|
*/
|
||||||
|
abstract class Step implements Comparable<Step> {
|
||||||
|
public final VibrationStepConductor conductor;
|
||||||
|
public final long startTime;
|
||||||
|
|
||||||
|
Step(VibrationStepConductor conductor, long startTime) {
|
||||||
|
this.conductor = conductor;
|
||||||
|
this.startTime = startTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Vibration getVibration() {
|
||||||
|
return conductor.getVibration();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if this step is a clean up step and not part of a {@link VibrationEffect} or
|
||||||
|
* {@link CombinedVibration}.
|
||||||
|
*/
|
||||||
|
public boolean isCleanUp() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Play this step, returning a (possibly empty) list of next steps. */
|
||||||
|
@NonNull
|
||||||
|
public abstract List<Step> play();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cancel this pending step and return a (possibly empty) list of clean-up steps that should
|
||||||
|
* be played to gracefully cancel this step.
|
||||||
|
*/
|
||||||
|
@NonNull
|
||||||
|
public abstract List<Step> cancel();
|
||||||
|
|
||||||
|
/** Cancel this pending step immediately, skipping any clean-up. */
|
||||||
|
public abstract void cancelImmediately();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the duration the vibrator was turned on when this step was played.
|
||||||
|
*
|
||||||
|
* @return A positive duration that the vibrator was turned on for by this step;
|
||||||
|
* Zero if the segment is not supported, the step was not played yet or vibrator was never
|
||||||
|
* turned on by this step; A negative value if the vibrator call has failed.
|
||||||
|
*/
|
||||||
|
public long getVibratorOnDuration() {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return true to run this step right after a vibrator has notified vibration completed,
|
||||||
|
* used to resume steps waiting on vibrator callbacks with a timeout.
|
||||||
|
*/
|
||||||
|
public boolean acceptVibratorCompleteCallback(int vibratorId) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the time in millis to wait before playing this step. This is performed
|
||||||
|
* while holding the queue lock, so should not rely on potentially slow operations.
|
||||||
|
*/
|
||||||
|
public long calculateWaitTime() {
|
||||||
|
if (startTime == Long.MAX_VALUE) {
|
||||||
|
// This step don't have a predefined start time, it's just marked to be executed
|
||||||
|
// after all other steps have finished.
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return Math.max(0, startTime - SystemClock.uptimeMillis());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int compareTo(Step o) {
|
||||||
|
return Long.compare(startTime, o.startTime);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
/*
|
||||||
|
* 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.Trace;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents a step to turn the vibrator off.
|
||||||
|
*
|
||||||
|
* <p>This runs after a timeout on the expected time the vibrator should have finished playing,
|
||||||
|
* and can be brought forward by vibrator complete callbacks. The step shouldn't be skipped, even
|
||||||
|
* if the vibrator-complete callback was received, as some implementations still rely on the
|
||||||
|
* "off" call to actually stop.
|
||||||
|
*/
|
||||||
|
final class TurnOffVibratorStep extends AbstractVibratorStep {
|
||||||
|
|
||||||
|
TurnOffVibratorStep(VibrationStepConductor conductor, long startTime,
|
||||||
|
VibratorController controller) {
|
||||||
|
super(conductor, startTime, controller, /* effect= */ null, /* index= */ -1, startTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isCleanUp() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<Step> cancel() {
|
||||||
|
return Arrays.asList(
|
||||||
|
new TurnOffVibratorStep(conductor, SystemClock.uptimeMillis(), controller));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void cancelImmediately() {
|
||||||
|
stopVibrating();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<Step> play() {
|
||||||
|
Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "TurnOffVibratorStep");
|
||||||
|
try {
|
||||||
|
stopVibrating();
|
||||||
|
return VibrationStepConductor.EMPTY_STEP_LIST;
|
||||||
|
} finally {
|
||||||
|
Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,355 @@
|
|||||||
|
/*
|
||||||
|
* 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.annotation.NonNull;
|
||||||
|
import android.annotation.Nullable;
|
||||||
|
import android.os.CombinedVibration;
|
||||||
|
import android.os.VibrationEffect;
|
||||||
|
import android.os.WorkSource;
|
||||||
|
import android.os.vibrator.PrebakedSegment;
|
||||||
|
import android.os.vibrator.PrimitiveSegment;
|
||||||
|
import android.os.vibrator.RampSegment;
|
||||||
|
import android.os.vibrator.VibrationEffectSegment;
|
||||||
|
import android.util.Slog;
|
||||||
|
import android.util.SparseArray;
|
||||||
|
|
||||||
|
import com.android.internal.annotations.GuardedBy;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.LinkedList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.PriorityQueue;
|
||||||
|
import java.util.Queue;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates and manages a queue of steps for performing a VibrationEffect, as well as coordinating
|
||||||
|
* dispatch of callbacks.
|
||||||
|
*/
|
||||||
|
final class VibrationStepConductor {
|
||||||
|
/**
|
||||||
|
* Extra timeout added to the end of each vibration step to ensure it finishes even when
|
||||||
|
* vibrator callbacks are lost.
|
||||||
|
*/
|
||||||
|
static final long CALLBACKS_EXTRA_TIMEOUT = 1_000;
|
||||||
|
/** Threshold to prevent the ramp off steps from trying to set extremely low amplitudes. */
|
||||||
|
static final float RAMP_OFF_AMPLITUDE_MIN = 1e-3f;
|
||||||
|
static final List<Step> EMPTY_STEP_LIST = new ArrayList<>();
|
||||||
|
|
||||||
|
final Object mLock = new Object();
|
||||||
|
|
||||||
|
// Used within steps.
|
||||||
|
public final VibrationSettings vibrationSettings;
|
||||||
|
public final DeviceVibrationEffectAdapter deviceEffectAdapter;
|
||||||
|
public final VibrationThread.VibratorManagerHooks vibratorManagerHooks;
|
||||||
|
|
||||||
|
private final WorkSource mWorkSource;
|
||||||
|
private final Vibration mVibration;
|
||||||
|
private final SparseArray<VibratorController> mVibrators = new SparseArray<>();
|
||||||
|
|
||||||
|
@GuardedBy("mLock")
|
||||||
|
private final PriorityQueue<Step> mNextSteps = new PriorityQueue<>();
|
||||||
|
@GuardedBy("mLock")
|
||||||
|
private final Queue<Step> mPendingOnVibratorCompleteSteps = new LinkedList<>();
|
||||||
|
@GuardedBy("mLock")
|
||||||
|
private final Queue<Integer> mCompletionNotifiedVibrators = new LinkedList<>();
|
||||||
|
|
||||||
|
@GuardedBy("mLock")
|
||||||
|
private int mPendingVibrateSteps;
|
||||||
|
@GuardedBy("mLock")
|
||||||
|
private int mConsumedStartVibrateSteps;
|
||||||
|
@GuardedBy("mLock")
|
||||||
|
private int mSuccessfulVibratorOnSteps;
|
||||||
|
@GuardedBy("mLock")
|
||||||
|
private boolean mWaitToProcessVibratorCompleteCallbacks;
|
||||||
|
|
||||||
|
VibrationStepConductor(Vibration vib, VibrationSettings vibrationSettings,
|
||||||
|
DeviceVibrationEffectAdapter effectAdapter,
|
||||||
|
SparseArray<VibratorController> availableVibrators,
|
||||||
|
VibrationThread.VibratorManagerHooks vibratorManagerHooks) {
|
||||||
|
this.mVibration = vib;
|
||||||
|
this.vibrationSettings = vibrationSettings;
|
||||||
|
this.deviceEffectAdapter = effectAdapter;
|
||||||
|
this.vibratorManagerHooks = vibratorManagerHooks;
|
||||||
|
this.mWorkSource = new WorkSource(mVibration.uid);
|
||||||
|
|
||||||
|
CombinedVibration effect = vib.getEffect();
|
||||||
|
for (int i = 0; i < availableVibrators.size(); i++) {
|
||||||
|
if (effect.hasVibrator(availableVibrators.keyAt(i))) {
|
||||||
|
mVibrators.put(availableVibrators.keyAt(i), availableVibrators.valueAt(i));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
AbstractVibratorStep nextVibrateStep(long startTime, VibratorController controller,
|
||||||
|
VibrationEffect.Composed effect, int segmentIndex,
|
||||||
|
long previousStepVibratorOffTimeout) {
|
||||||
|
if (segmentIndex >= effect.getSegments().size()) {
|
||||||
|
segmentIndex = effect.getRepeatIndex();
|
||||||
|
}
|
||||||
|
if (segmentIndex < 0) {
|
||||||
|
// No more segments to play, last step is to complete the vibration on this vibrator.
|
||||||
|
return new CompleteEffectVibratorStep(this, startTime, /* cancelled= */ false,
|
||||||
|
controller, previousStepVibratorOffTimeout);
|
||||||
|
}
|
||||||
|
|
||||||
|
VibrationEffectSegment segment = effect.getSegments().get(segmentIndex);
|
||||||
|
if (segment instanceof PrebakedSegment) {
|
||||||
|
return new PerformPrebakedVibratorStep(this, startTime, controller, effect,
|
||||||
|
segmentIndex, previousStepVibratorOffTimeout);
|
||||||
|
}
|
||||||
|
if (segment instanceof PrimitiveSegment) {
|
||||||
|
return new ComposePrimitivesVibratorStep(this, startTime, controller, effect,
|
||||||
|
segmentIndex, previousStepVibratorOffTimeout);
|
||||||
|
}
|
||||||
|
if (segment instanceof RampSegment) {
|
||||||
|
return new ComposePwleVibratorStep(this, startTime, controller, effect, segmentIndex,
|
||||||
|
previousStepVibratorOffTimeout);
|
||||||
|
}
|
||||||
|
return new SetAmplitudeVibratorStep(this, startTime, controller, effect, segmentIndex,
|
||||||
|
previousStepVibratorOffTimeout);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void initializeForEffect(@NonNull CombinedVibration.Sequential vibration) {
|
||||||
|
synchronized (mLock) {
|
||||||
|
mPendingVibrateSteps++;
|
||||||
|
mNextSteps.offer(new StartSequentialEffectStep(this, vibration));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Vibration getVibration() {
|
||||||
|
return mVibration;
|
||||||
|
}
|
||||||
|
|
||||||
|
public WorkSource getWorkSource() {
|
||||||
|
return mWorkSource;
|
||||||
|
}
|
||||||
|
|
||||||
|
SparseArray<VibratorController> getVibrators() {
|
||||||
|
return mVibrators;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isFinished() {
|
||||||
|
synchronized (mLock) {
|
||||||
|
return mPendingOnVibratorCompleteSteps.isEmpty() && mNextSteps.isEmpty();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculate the {@link Vibration.Status} based on the current queue state and the expected
|
||||||
|
* number of {@link StartSequentialEffectStep} to be played.
|
||||||
|
*/
|
||||||
|
public Vibration.Status calculateVibrationStatus(int expectedStartVibrateSteps) {
|
||||||
|
synchronized (mLock) {
|
||||||
|
if (mPendingVibrateSteps > 0
|
||||||
|
|| mConsumedStartVibrateSteps < expectedStartVibrateSteps) {
|
||||||
|
return Vibration.Status.RUNNING;
|
||||||
|
}
|
||||||
|
if (mSuccessfulVibratorOnSteps > 0) {
|
||||||
|
return Vibration.Status.FINISHED;
|
||||||
|
}
|
||||||
|
// If no step was able to turn the vibrator ON successfully.
|
||||||
|
return Vibration.Status.IGNORED_UNSUPPORTED;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Returns the time in millis to wait before calling {@link #runNextStep()}. */
|
||||||
|
@GuardedBy("mLock")
|
||||||
|
public long getWaitMillisBeforeNextStepLocked() {
|
||||||
|
if (!mPendingOnVibratorCompleteSteps.isEmpty()) {
|
||||||
|
// Steps resumed by vibrator complete callback should be played right away.
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
Step nextStep = mNextSteps.peek();
|
||||||
|
return nextStep == null ? 0 : nextStep.calculateWaitTime();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Play and remove the step at the top of this queue, and also adds the next steps generated
|
||||||
|
* to be played next.
|
||||||
|
*/
|
||||||
|
public void runNextStep() {
|
||||||
|
// Vibrator callbacks should wait until the polled step is played and the next steps are
|
||||||
|
// added back to the queue, so they can handle the callback.
|
||||||
|
markWaitToProcessVibratorCallbacks();
|
||||||
|
try {
|
||||||
|
Step nextStep = pollNext();
|
||||||
|
if (nextStep != null) {
|
||||||
|
// This might turn on the vibrator and have a HAL latency. Execute this outside
|
||||||
|
// any lock to avoid blocking other interactions with the thread.
|
||||||
|
List<Step> nextSteps = nextStep.play();
|
||||||
|
synchronized (mLock) {
|
||||||
|
if (nextStep.getVibratorOnDuration() > 0) {
|
||||||
|
mSuccessfulVibratorOnSteps++;
|
||||||
|
}
|
||||||
|
if (nextStep instanceof StartSequentialEffectStep) {
|
||||||
|
mConsumedStartVibrateSteps++;
|
||||||
|
}
|
||||||
|
if (!nextStep.isCleanUp()) {
|
||||||
|
mPendingVibrateSteps--;
|
||||||
|
}
|
||||||
|
for (int i = 0; i < nextSteps.size(); i++) {
|
||||||
|
mPendingVibrateSteps += nextSteps.get(i).isCleanUp() ? 0 : 1;
|
||||||
|
}
|
||||||
|
mNextSteps.addAll(nextSteps);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
synchronized (mLock) {
|
||||||
|
processVibratorCompleteCallbacksLocked();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Notify the vibrator completion.
|
||||||
|
*
|
||||||
|
* <p>This is a lightweight method that do not trigger any operation from {@link
|
||||||
|
* VibratorController}, so it can be called directly from a native callback.
|
||||||
|
*/
|
||||||
|
@GuardedBy("mLock")
|
||||||
|
private void notifyVibratorCompleteLocked(int vibratorId) {
|
||||||
|
mCompletionNotifiedVibrators.offer(vibratorId);
|
||||||
|
if (!mWaitToProcessVibratorCompleteCallbacks) {
|
||||||
|
// No step is being played or cancelled now, process the callback right away.
|
||||||
|
processVibratorCompleteCallbacksLocked();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void notifyVibratorComplete(int vibratorId) {
|
||||||
|
synchronized (mLock) {
|
||||||
|
if (VibrationThread.DEBUG) {
|
||||||
|
Slog.d(VibrationThread.TAG,
|
||||||
|
"Vibration complete reported by vibrator " + vibratorId);
|
||||||
|
}
|
||||||
|
notifyVibratorCompleteLocked(vibratorId);
|
||||||
|
mLock.notify();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void notifySyncedVibrationComplete() {
|
||||||
|
synchronized (mLock) {
|
||||||
|
if (VibrationThread.DEBUG) {
|
||||||
|
Slog.d(VibrationThread.TAG,
|
||||||
|
"Synced vibration complete reported by vibrator manager");
|
||||||
|
}
|
||||||
|
for (int i = 0; i < mVibrators.size(); i++) {
|
||||||
|
notifyVibratorCompleteLocked(mVibrators.keyAt(i));
|
||||||
|
}
|
||||||
|
mLock.notify();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cancel the current queue, replacing all remaining steps with respective clean-up steps.
|
||||||
|
*
|
||||||
|
* <p>This will remove all steps and replace them with respective
|
||||||
|
* {@link Step#cancel()}.
|
||||||
|
*/
|
||||||
|
public void cancel() {
|
||||||
|
// Vibrator callbacks should wait until all steps from the queue are properly cancelled
|
||||||
|
// and clean up steps are added back to the queue, so they can handle the callback.
|
||||||
|
markWaitToProcessVibratorCallbacks();
|
||||||
|
try {
|
||||||
|
List<Step> cleanUpSteps = new ArrayList<>();
|
||||||
|
Step step;
|
||||||
|
while ((step = pollNext()) != null) {
|
||||||
|
cleanUpSteps.addAll(step.cancel());
|
||||||
|
}
|
||||||
|
synchronized (mLock) {
|
||||||
|
// All steps generated by Step.cancel() should be clean-up steps.
|
||||||
|
mPendingVibrateSteps = 0;
|
||||||
|
mNextSteps.addAll(cleanUpSteps);
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
synchronized (mLock) {
|
||||||
|
processVibratorCompleteCallbacksLocked();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cancel the current queue immediately, clearing all remaining steps and skipping clean-up.
|
||||||
|
*
|
||||||
|
* <p>This will remove and trigger {@link Step#cancelImmediately()} in all steps, in order.
|
||||||
|
*/
|
||||||
|
public void cancelImmediately() {
|
||||||
|
// Vibrator callbacks should wait until all steps from the queue are properly cancelled.
|
||||||
|
markWaitToProcessVibratorCallbacks();
|
||||||
|
try {
|
||||||
|
Step step;
|
||||||
|
while ((step = pollNext()) != null) {
|
||||||
|
// This might turn off the vibrator and have a HAL latency. Execute this outside
|
||||||
|
// any lock to avoid blocking other interactions with the thread.
|
||||||
|
step.cancelImmediately();
|
||||||
|
}
|
||||||
|
synchronized (mLock) {
|
||||||
|
mPendingVibrateSteps = 0;
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
synchronized (mLock) {
|
||||||
|
processVibratorCompleteCallbacksLocked();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private Step pollNext() {
|
||||||
|
synchronized (mLock) {
|
||||||
|
// Prioritize the steps resumed by a vibrator complete callback.
|
||||||
|
if (!mPendingOnVibratorCompleteSteps.isEmpty()) {
|
||||||
|
return mPendingOnVibratorCompleteSteps.poll();
|
||||||
|
}
|
||||||
|
return mNextSteps.poll();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void markWaitToProcessVibratorCallbacks() {
|
||||||
|
synchronized (mLock) {
|
||||||
|
mWaitToProcessVibratorCompleteCallbacks = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Notify the step in this queue that should be resumed by the vibrator completion
|
||||||
|
* callback and keep it separate to be consumed by {@link #runNextStep()}.
|
||||||
|
*
|
||||||
|
* <p>This is a lightweight method that do not trigger any operation from {@link
|
||||||
|
* VibratorController}, so it can be called directly from a native callback.
|
||||||
|
*
|
||||||
|
* <p>This assumes only one of the next steps is waiting on this given vibrator, so the
|
||||||
|
* first step found will be resumed by this method, in no particular order.
|
||||||
|
*/
|
||||||
|
@GuardedBy("mLock")
|
||||||
|
private void processVibratorCompleteCallbacksLocked() {
|
||||||
|
mWaitToProcessVibratorCompleteCallbacks = false;
|
||||||
|
while (!mCompletionNotifiedVibrators.isEmpty()) {
|
||||||
|
int vibratorId = mCompletionNotifiedVibrators.poll();
|
||||||
|
Iterator<Step> it = mNextSteps.iterator();
|
||||||
|
while (it.hasNext()) {
|
||||||
|
Step step = it.next();
|
||||||
|
if (step.acceptVibratorCompleteCallback(vibratorId)) {
|
||||||
|
it.remove();
|
||||||
|
mPendingOnVibratorCompleteSteps.offer(step);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -42,6 +42,7 @@ import android.os.IVibratorStateListener;
|
|||||||
import android.os.Looper;
|
import android.os.Looper;
|
||||||
import android.os.PowerManager;
|
import android.os.PowerManager;
|
||||||
import android.os.Process;
|
import android.os.Process;
|
||||||
|
import android.os.RemoteException;
|
||||||
import android.os.ResultReceiver;
|
import android.os.ResultReceiver;
|
||||||
import android.os.ServiceManager;
|
import android.os.ServiceManager;
|
||||||
import android.os.ShellCallback;
|
import android.os.ShellCallback;
|
||||||
@@ -60,6 +61,7 @@ import com.android.internal.annotations.GuardedBy;
|
|||||||
import com.android.internal.annotations.VisibleForTesting;
|
import com.android.internal.annotations.VisibleForTesting;
|
||||||
import com.android.internal.app.IBatteryStats;
|
import com.android.internal.app.IBatteryStats;
|
||||||
import com.android.internal.util.DumpUtils;
|
import com.android.internal.util.DumpUtils;
|
||||||
|
import com.android.internal.util.FrameworkStatsLog;
|
||||||
import com.android.server.LocalServices;
|
import com.android.server.LocalServices;
|
||||||
import com.android.server.SystemService;
|
import com.android.server.SystemService;
|
||||||
|
|
||||||
@@ -88,6 +90,9 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
|
|||||||
VibrationAttributes.FLAG_BYPASS_INTERRUPTION_POLICY
|
VibrationAttributes.FLAG_BYPASS_INTERRUPTION_POLICY
|
||||||
| VibrationAttributes.FLAG_BYPASS_USER_VIBRATION_INTENSITY_OFF;
|
| VibrationAttributes.FLAG_BYPASS_USER_VIBRATION_INTENSITY_OFF;
|
||||||
|
|
||||||
|
/** Fixed large duration used to note repeating vibrations to {@link IBatteryStats}. */
|
||||||
|
private static final long BATTERY_STATS_REPEATING_VIBRATION_DURATION = 5_000;
|
||||||
|
|
||||||
/** Lifecycle responsible for initializing this class at the right system server phases. */
|
/** Lifecycle responsible for initializing this class at the right system server phases. */
|
||||||
public static class Lifecycle extends SystemService {
|
public static class Lifecycle extends SystemService {
|
||||||
private VibratorManagerService mService;
|
private VibratorManagerService mService;
|
||||||
@@ -201,8 +206,7 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
|
|||||||
mSystemUiPackage = LocalServices.getService(PackageManagerInternal.class)
|
mSystemUiPackage = LocalServices.getService(PackageManagerInternal.class)
|
||||||
.getSystemUiServiceComponent().getPackageName();
|
.getSystemUiServiceComponent().getPackageName();
|
||||||
|
|
||||||
mBatteryStatsService = IBatteryStats.Stub.asInterface(ServiceManager.getService(
|
mBatteryStatsService = injector.getBatteryStatsService();
|
||||||
BatteryStats.SERVICE_NAME));
|
|
||||||
|
|
||||||
mAppOps = mContext.getSystemService(AppOpsManager.class);
|
mAppOps = mContext.getSystemService(AppOpsManager.class);
|
||||||
|
|
||||||
@@ -634,7 +638,7 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
|
|||||||
}
|
}
|
||||||
|
|
||||||
VibrationThread vibThread = new VibrationThread(vib, mVibrationSettings,
|
VibrationThread vibThread = new VibrationThread(vib, mVibrationSettings,
|
||||||
mDeviceVibrationEffectAdapter, mVibrators, mWakeLock, mBatteryStatsService,
|
mDeviceVibrationEffectAdapter, mVibrators, mWakeLock,
|
||||||
mVibrationThreadCallbacks);
|
mVibrationThreadCallbacks);
|
||||||
|
|
||||||
if (mCurrentVibration == null) {
|
if (mCurrentVibration == null) {
|
||||||
@@ -1105,6 +1109,11 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
|
|||||||
return new Handler(looper);
|
return new Handler(looper);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
IBatteryStats getBatteryStatsService() {
|
||||||
|
return IBatteryStats.Stub.asInterface(ServiceManager.getService(
|
||||||
|
BatteryStats.SERVICE_NAME));
|
||||||
|
}
|
||||||
|
|
||||||
VibratorController createVibratorController(int vibratorId,
|
VibratorController createVibratorController(int vibratorId,
|
||||||
VibratorController.OnVibrationCompleteListener listener) {
|
VibratorController.OnVibrationCompleteListener listener) {
|
||||||
return new VibratorController(vibratorId, listener);
|
return new VibratorController(vibratorId, listener);
|
||||||
@@ -1140,6 +1149,36 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
|
|||||||
mNativeWrapper.cancelSynced();
|
mNativeWrapper.cancelSynced();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void noteVibratorOn(int uid, long duration) {
|
||||||
|
try {
|
||||||
|
if (duration <= 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (duration == Long.MAX_VALUE) {
|
||||||
|
// Repeating duration has started. Report a fixed duration here, noteVibratorOff
|
||||||
|
// should be called when this is cancelled.
|
||||||
|
duration = BATTERY_STATS_REPEATING_VIBRATION_DURATION;
|
||||||
|
}
|
||||||
|
mBatteryStatsService.noteVibratorOn(uid, duration);
|
||||||
|
FrameworkStatsLog.write_non_chained(FrameworkStatsLog.VIBRATOR_STATE_CHANGED,
|
||||||
|
uid, null, FrameworkStatsLog.VIBRATOR_STATE_CHANGED__STATE__ON,
|
||||||
|
duration);
|
||||||
|
} catch (RemoteException e) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void noteVibratorOff(int uid) {
|
||||||
|
try {
|
||||||
|
mBatteryStatsService.noteVibratorOff(uid);
|
||||||
|
FrameworkStatsLog.write_non_chained(FrameworkStatsLog.VIBRATOR_STATE_CHANGED,
|
||||||
|
uid, null, FrameworkStatsLog.VIBRATOR_STATE_CHANGED__STATE__OFF,
|
||||||
|
/* duration= */ 0);
|
||||||
|
} catch (RemoteException e) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onVibrationCompleted(long vibrationId, Vibration.Status status) {
|
public void onVibrationCompleted(long vibrationId, Vibration.Status status) {
|
||||||
if (DEBUG) {
|
if (DEBUG) {
|
||||||
|
|||||||
@@ -61,8 +61,6 @@ import android.util.SparseArray;
|
|||||||
|
|
||||||
import androidx.test.InstrumentationRegistry;
|
import androidx.test.InstrumentationRegistry;
|
||||||
|
|
||||||
import com.android.internal.app.IBatteryStats;
|
|
||||||
|
|
||||||
import org.junit.After;
|
import org.junit.After;
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.Rule;
|
import org.junit.Rule;
|
||||||
@@ -106,8 +104,6 @@ public class VibrationThreadTest {
|
|||||||
@Mock
|
@Mock
|
||||||
private IBinder mVibrationToken;
|
private IBinder mVibrationToken;
|
||||||
@Mock
|
@Mock
|
||||||
private IBatteryStats mIBatteryStatsMock;
|
|
||||||
@Mock
|
|
||||||
private VibrationConfig mVibrationConfigMock;
|
private VibrationConfig mVibrationConfigMock;
|
||||||
|
|
||||||
private final Map<Integer, FakeVibratorControllerProvider> mVibratorProviders = new HashMap<>();
|
private final Map<Integer, FakeVibratorControllerProvider> mVibratorProviders = new HashMap<>();
|
||||||
@@ -178,8 +174,8 @@ public class VibrationThreadTest {
|
|||||||
VibrationThread thread = startThreadAndDispatcher(vibrationId, effect);
|
VibrationThread thread = startThreadAndDispatcher(vibrationId, effect);
|
||||||
waitForCompletion(thread);
|
waitForCompletion(thread);
|
||||||
|
|
||||||
verify(mIBatteryStatsMock).noteVibratorOn(eq(UID), eq(10L));
|
verify(mManagerHooks).noteVibratorOn(eq(UID), eq(10L));
|
||||||
verify(mIBatteryStatsMock).noteVibratorOff(eq(UID));
|
verify(mManagerHooks).noteVibratorOff(eq(UID));
|
||||||
verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibrationId));
|
verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibrationId));
|
||||||
verifyCallbacksTriggered(vibrationId, Vibration.Status.FINISHED);
|
verifyCallbacksTriggered(vibrationId, Vibration.Status.FINISHED);
|
||||||
assertFalse(thread.getVibrators().get(VIBRATOR_ID).isVibrating());
|
assertFalse(thread.getVibrators().get(VIBRATOR_ID).isVibrating());
|
||||||
@@ -197,8 +193,8 @@ public class VibrationThreadTest {
|
|||||||
VibrationThread thread = startThreadAndDispatcher(vibrationId, effect);
|
VibrationThread thread = startThreadAndDispatcher(vibrationId, effect);
|
||||||
waitForCompletion(thread);
|
waitForCompletion(thread);
|
||||||
|
|
||||||
verify(mIBatteryStatsMock).noteVibratorOn(eq(UID), eq(10L));
|
verify(mManagerHooks).noteVibratorOn(eq(UID), eq(10L));
|
||||||
verify(mIBatteryStatsMock).noteVibratorOff(eq(UID));
|
verify(mManagerHooks).noteVibratorOff(eq(UID));
|
||||||
verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibrationId));
|
verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibrationId));
|
||||||
verifyCallbacksTriggered(vibrationId, Vibration.Status.FINISHED);
|
verifyCallbacksTriggered(vibrationId, Vibration.Status.FINISHED);
|
||||||
assertFalse(thread.getVibrators().get(VIBRATOR_ID).isVibrating());
|
assertFalse(thread.getVibrators().get(VIBRATOR_ID).isVibrating());
|
||||||
@@ -219,8 +215,8 @@ public class VibrationThreadTest {
|
|||||||
VibrationThread thread = startThreadAndDispatcher(vibrationId, effect);
|
VibrationThread thread = startThreadAndDispatcher(vibrationId, effect);
|
||||||
waitForCompletion(thread);
|
waitForCompletion(thread);
|
||||||
|
|
||||||
verify(mIBatteryStatsMock).noteVibratorOn(eq(UID), eq(15L));
|
verify(mManagerHooks).noteVibratorOn(eq(UID), eq(15L));
|
||||||
verify(mIBatteryStatsMock).noteVibratorOff(eq(UID));
|
verify(mManagerHooks).noteVibratorOff(eq(UID));
|
||||||
verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibrationId));
|
verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibrationId));
|
||||||
verifyCallbacksTriggered(vibrationId, Vibration.Status.FINISHED);
|
verifyCallbacksTriggered(vibrationId, Vibration.Status.FINISHED);
|
||||||
assertFalse(thread.getVibrators().get(VIBRATOR_ID).isVibrating());
|
assertFalse(thread.getVibrators().get(VIBRATOR_ID).isVibrating());
|
||||||
@@ -252,8 +248,8 @@ public class VibrationThreadTest {
|
|||||||
thread.cancel();
|
thread.cancel();
|
||||||
waitForCompletion(thread);
|
waitForCompletion(thread);
|
||||||
|
|
||||||
verify(mIBatteryStatsMock).noteVibratorOn(eq(UID), anyLong());
|
verify(mManagerHooks).noteVibratorOn(eq(UID), anyLong());
|
||||||
verify(mIBatteryStatsMock).noteVibratorOff(eq(UID));
|
verify(mManagerHooks).noteVibratorOff(eq(UID));
|
||||||
verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED);
|
verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED);
|
||||||
assertFalse(thread.getVibrators().get(VIBRATOR_ID).isVibrating());
|
assertFalse(thread.getVibrators().get(VIBRATOR_ID).isVibrating());
|
||||||
|
|
||||||
@@ -404,8 +400,8 @@ public class VibrationThreadTest {
|
|||||||
VibrationThread thread = startThreadAndDispatcher(vibrationId, effect);
|
VibrationThread thread = startThreadAndDispatcher(vibrationId, effect);
|
||||||
waitForCompletion(thread);
|
waitForCompletion(thread);
|
||||||
|
|
||||||
verify(mIBatteryStatsMock).noteVibratorOn(eq(UID), eq(20L));
|
verify(mManagerHooks).noteVibratorOn(eq(UID), eq(20L));
|
||||||
verify(mIBatteryStatsMock).noteVibratorOff(eq(UID));
|
verify(mManagerHooks).noteVibratorOff(eq(UID));
|
||||||
verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibrationId));
|
verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibrationId));
|
||||||
verifyCallbacksTriggered(vibrationId, Vibration.Status.FINISHED);
|
verifyCallbacksTriggered(vibrationId, Vibration.Status.FINISHED);
|
||||||
assertFalse(thread.getVibrators().get(VIBRATOR_ID).isVibrating());
|
assertFalse(thread.getVibrators().get(VIBRATOR_ID).isVibrating());
|
||||||
@@ -427,8 +423,8 @@ public class VibrationThreadTest {
|
|||||||
VibrationThread thread = startThreadAndDispatcher(vibration);
|
VibrationThread thread = startThreadAndDispatcher(vibration);
|
||||||
waitForCompletion(thread);
|
waitForCompletion(thread);
|
||||||
|
|
||||||
verify(mIBatteryStatsMock).noteVibratorOn(eq(UID), eq(10L));
|
verify(mManagerHooks).noteVibratorOn(eq(UID), eq(10L));
|
||||||
verify(mIBatteryStatsMock).noteVibratorOff(eq(UID));
|
verify(mManagerHooks).noteVibratorOff(eq(UID));
|
||||||
verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibrationId));
|
verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibrationId));
|
||||||
verifyCallbacksTriggered(vibrationId, Vibration.Status.FINISHED);
|
verifyCallbacksTriggered(vibrationId, Vibration.Status.FINISHED);
|
||||||
assertFalse(thread.getVibrators().get(VIBRATOR_ID).isVibrating());
|
assertFalse(thread.getVibrators().get(VIBRATOR_ID).isVibrating());
|
||||||
@@ -446,8 +442,8 @@ public class VibrationThreadTest {
|
|||||||
VibrationThread thread = startThreadAndDispatcher(vibrationId, effect);
|
VibrationThread thread = startThreadAndDispatcher(vibrationId, effect);
|
||||||
waitForCompletion(thread);
|
waitForCompletion(thread);
|
||||||
|
|
||||||
verify(mIBatteryStatsMock, never()).noteVibratorOn(eq(UID), anyLong());
|
verify(mManagerHooks, never()).noteVibratorOn(eq(UID), anyLong());
|
||||||
verify(mIBatteryStatsMock, never()).noteVibratorOff(eq(UID));
|
verify(mManagerHooks, never()).noteVibratorOff(eq(UID));
|
||||||
verify(mControllerCallbacks, never()).onComplete(eq(VIBRATOR_ID), eq(vibrationId));
|
verify(mControllerCallbacks, never()).onComplete(eq(VIBRATOR_ID), eq(vibrationId));
|
||||||
verifyCallbacksTriggered(vibrationId, Vibration.Status.IGNORED_UNSUPPORTED);
|
verifyCallbacksTriggered(vibrationId, Vibration.Status.IGNORED_UNSUPPORTED);
|
||||||
assertTrue(mVibratorProviders.get(VIBRATOR_ID).getEffectSegments().isEmpty());
|
assertTrue(mVibratorProviders.get(VIBRATOR_ID).getEffectSegments().isEmpty());
|
||||||
@@ -466,8 +462,8 @@ public class VibrationThreadTest {
|
|||||||
VibrationThread thread = startThreadAndDispatcher(vibrationId, effect);
|
VibrationThread thread = startThreadAndDispatcher(vibrationId, effect);
|
||||||
waitForCompletion(thread);
|
waitForCompletion(thread);
|
||||||
|
|
||||||
verify(mIBatteryStatsMock).noteVibratorOn(eq(UID), eq(40L));
|
verify(mManagerHooks).noteVibratorOn(eq(UID), eq(40L));
|
||||||
verify(mIBatteryStatsMock).noteVibratorOff(eq(UID));
|
verify(mManagerHooks).noteVibratorOff(eq(UID));
|
||||||
verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibrationId));
|
verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibrationId));
|
||||||
verifyCallbacksTriggered(vibrationId, Vibration.Status.FINISHED);
|
verifyCallbacksTriggered(vibrationId, Vibration.Status.FINISHED);
|
||||||
assertFalse(thread.getVibrators().get(VIBRATOR_ID).isVibrating());
|
assertFalse(thread.getVibrators().get(VIBRATOR_ID).isVibrating());
|
||||||
@@ -486,8 +482,8 @@ public class VibrationThreadTest {
|
|||||||
VibrationThread thread = startThreadAndDispatcher(vibrationId, effect);
|
VibrationThread thread = startThreadAndDispatcher(vibrationId, effect);
|
||||||
waitForCompletion(thread);
|
waitForCompletion(thread);
|
||||||
|
|
||||||
verify(mIBatteryStatsMock, never()).noteVibratorOn(eq(UID), anyLong());
|
verify(mManagerHooks, never()).noteVibratorOn(eq(UID), anyLong());
|
||||||
verify(mIBatteryStatsMock, never()).noteVibratorOff(eq(UID));
|
verify(mManagerHooks, never()).noteVibratorOff(eq(UID));
|
||||||
verify(mControllerCallbacks, never()).onComplete(eq(VIBRATOR_ID), eq(vibrationId));
|
verify(mControllerCallbacks, never()).onComplete(eq(VIBRATOR_ID), eq(vibrationId));
|
||||||
verifyCallbacksTriggered(vibrationId, Vibration.Status.IGNORED_UNSUPPORTED);
|
verifyCallbacksTriggered(vibrationId, Vibration.Status.IGNORED_UNSUPPORTED);
|
||||||
assertTrue(mVibratorProviders.get(VIBRATOR_ID).getEffectSegments().isEmpty());
|
assertTrue(mVibratorProviders.get(VIBRATOR_ID).getEffectSegments().isEmpty());
|
||||||
@@ -536,8 +532,8 @@ public class VibrationThreadTest {
|
|||||||
waitForCompletion(thread);
|
waitForCompletion(thread);
|
||||||
|
|
||||||
// Use first duration the vibrator is turned on since we cannot estimate the clicks.
|
// Use first duration the vibrator is turned on since we cannot estimate the clicks.
|
||||||
verify(mIBatteryStatsMock).noteVibratorOn(eq(UID), eq(10L));
|
verify(mManagerHooks).noteVibratorOn(eq(UID), eq(10L));
|
||||||
verify(mIBatteryStatsMock).noteVibratorOff(eq(UID));
|
verify(mManagerHooks).noteVibratorOff(eq(UID));
|
||||||
verify(mControllerCallbacks, times(4)).onComplete(eq(VIBRATOR_ID), eq(vibrationId));
|
verify(mControllerCallbacks, times(4)).onComplete(eq(VIBRATOR_ID), eq(vibrationId));
|
||||||
verifyCallbacksTriggered(vibrationId, Vibration.Status.FINISHED);
|
verifyCallbacksTriggered(vibrationId, Vibration.Status.FINISHED);
|
||||||
assertFalse(thread.getVibrators().get(VIBRATOR_ID).isVibrating());
|
assertFalse(thread.getVibrators().get(VIBRATOR_ID).isVibrating());
|
||||||
@@ -573,8 +569,8 @@ public class VibrationThreadTest {
|
|||||||
VibrationThread thread = startThreadAndDispatcher(vibrationId, effect);
|
VibrationThread thread = startThreadAndDispatcher(vibrationId, effect);
|
||||||
waitForCompletion(thread);
|
waitForCompletion(thread);
|
||||||
|
|
||||||
verify(mIBatteryStatsMock).noteVibratorOn(eq(UID), eq(100L));
|
verify(mManagerHooks).noteVibratorOn(eq(UID), eq(100L));
|
||||||
verify(mIBatteryStatsMock).noteVibratorOff(eq(UID));
|
verify(mManagerHooks).noteVibratorOff(eq(UID));
|
||||||
verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibrationId));
|
verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibrationId));
|
||||||
verifyCallbacksTriggered(vibrationId, Vibration.Status.FINISHED);
|
verifyCallbacksTriggered(vibrationId, Vibration.Status.FINISHED);
|
||||||
assertFalse(thread.getVibrators().get(VIBRATOR_ID).isVibrating());
|
assertFalse(thread.getVibrators().get(VIBRATOR_ID).isVibrating());
|
||||||
@@ -666,8 +662,8 @@ public class VibrationThreadTest {
|
|||||||
VibrationThread thread = startThreadAndDispatcher(vibrationId, effect);
|
VibrationThread thread = startThreadAndDispatcher(vibrationId, effect);
|
||||||
waitForCompletion(thread);
|
waitForCompletion(thread);
|
||||||
|
|
||||||
verify(mIBatteryStatsMock).noteVibratorOn(eq(UID), eq(20L));
|
verify(mManagerHooks).noteVibratorOn(eq(UID), eq(20L));
|
||||||
verify(mIBatteryStatsMock).noteVibratorOff(eq(UID));
|
verify(mManagerHooks).noteVibratorOff(eq(UID));
|
||||||
verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibrationId));
|
verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibrationId));
|
||||||
verify(mControllerCallbacks, never()).onComplete(eq(2), eq(vibrationId));
|
verify(mControllerCallbacks, never()).onComplete(eq(2), eq(vibrationId));
|
||||||
verifyCallbacksTriggered(vibrationId, Vibration.Status.FINISHED);
|
verifyCallbacksTriggered(vibrationId, Vibration.Status.FINISHED);
|
||||||
@@ -690,8 +686,8 @@ public class VibrationThreadTest {
|
|||||||
VibrationThread thread = startThreadAndDispatcher(vibrationId, effect);
|
VibrationThread thread = startThreadAndDispatcher(vibrationId, effect);
|
||||||
waitForCompletion(thread);
|
waitForCompletion(thread);
|
||||||
|
|
||||||
verify(mIBatteryStatsMock).noteVibratorOn(eq(UID), eq(20L));
|
verify(mManagerHooks).noteVibratorOn(eq(UID), eq(20L));
|
||||||
verify(mIBatteryStatsMock).noteVibratorOff(eq(UID));
|
verify(mManagerHooks).noteVibratorOff(eq(UID));
|
||||||
verify(mControllerCallbacks).onComplete(eq(1), eq(vibrationId));
|
verify(mControllerCallbacks).onComplete(eq(1), eq(vibrationId));
|
||||||
verify(mControllerCallbacks).onComplete(eq(2), eq(vibrationId));
|
verify(mControllerCallbacks).onComplete(eq(2), eq(vibrationId));
|
||||||
verify(mControllerCallbacks).onComplete(eq(3), eq(vibrationId));
|
verify(mControllerCallbacks).onComplete(eq(3), eq(vibrationId));
|
||||||
@@ -728,8 +724,8 @@ public class VibrationThreadTest {
|
|||||||
VibrationThread thread = startThreadAndDispatcher(vibrationId, effect);
|
VibrationThread thread = startThreadAndDispatcher(vibrationId, effect);
|
||||||
waitForCompletion(thread);
|
waitForCompletion(thread);
|
||||||
|
|
||||||
verify(mIBatteryStatsMock).noteVibratorOn(eq(UID), eq(20L));
|
verify(mManagerHooks).noteVibratorOn(eq(UID), eq(20L));
|
||||||
verify(mIBatteryStatsMock).noteVibratorOff(eq(UID));
|
verify(mManagerHooks).noteVibratorOff(eq(UID));
|
||||||
verify(mControllerCallbacks).onComplete(eq(1), eq(vibrationId));
|
verify(mControllerCallbacks).onComplete(eq(1), eq(vibrationId));
|
||||||
verify(mControllerCallbacks).onComplete(eq(2), eq(vibrationId));
|
verify(mControllerCallbacks).onComplete(eq(2), eq(vibrationId));
|
||||||
verify(mControllerCallbacks).onComplete(eq(3), eq(vibrationId));
|
verify(mControllerCallbacks).onComplete(eq(3), eq(vibrationId));
|
||||||
@@ -777,13 +773,13 @@ public class VibrationThreadTest {
|
|||||||
controllerVerifier.verify(mControllerCallbacks).onComplete(eq(1), eq(vibrationId));
|
controllerVerifier.verify(mControllerCallbacks).onComplete(eq(1), eq(vibrationId));
|
||||||
controllerVerifier.verify(mControllerCallbacks).onComplete(eq(2), eq(vibrationId));
|
controllerVerifier.verify(mControllerCallbacks).onComplete(eq(2), eq(vibrationId));
|
||||||
|
|
||||||
InOrder batterVerifier = inOrder(mIBatteryStatsMock);
|
InOrder batteryVerifier = inOrder(mManagerHooks);
|
||||||
batterVerifier.verify(mIBatteryStatsMock).noteVibratorOn(eq(UID), eq(20L));
|
batteryVerifier.verify(mManagerHooks).noteVibratorOn(eq(UID), eq(20L));
|
||||||
batterVerifier.verify(mIBatteryStatsMock).noteVibratorOff(eq(UID));
|
batteryVerifier.verify(mManagerHooks).noteVibratorOff(eq(UID));
|
||||||
batterVerifier.verify(mIBatteryStatsMock).noteVibratorOn(eq(UID), eq(10L));
|
batteryVerifier.verify(mManagerHooks).noteVibratorOn(eq(UID), eq(10L));
|
||||||
batterVerifier.verify(mIBatteryStatsMock).noteVibratorOff(eq(UID));
|
batteryVerifier.verify(mManagerHooks).noteVibratorOff(eq(UID));
|
||||||
batterVerifier.verify(mIBatteryStatsMock).noteVibratorOn(eq(UID), eq(20L));
|
batteryVerifier.verify(mManagerHooks).noteVibratorOn(eq(UID), eq(20L));
|
||||||
batterVerifier.verify(mIBatteryStatsMock).noteVibratorOff(eq(UID));
|
batteryVerifier.verify(mManagerHooks).noteVibratorOff(eq(UID));
|
||||||
|
|
||||||
verifyCallbacksTriggered(vibrationId, Vibration.Status.FINISHED);
|
verifyCallbacksTriggered(vibrationId, Vibration.Status.FINISHED);
|
||||||
assertFalse(thread.getVibrators().get(1).isVibrating());
|
assertFalse(thread.getVibrators().get(1).isVibrating());
|
||||||
@@ -952,8 +948,8 @@ public class VibrationThreadTest {
|
|||||||
|
|
||||||
waitForCompletion(thread);
|
waitForCompletion(thread);
|
||||||
|
|
||||||
verify(mIBatteryStatsMock).noteVibratorOn(eq(UID), eq(80L));
|
verify(mManagerHooks).noteVibratorOn(eq(UID), eq(80L));
|
||||||
verify(mIBatteryStatsMock).noteVibratorOff(eq(UID));
|
verify(mManagerHooks).noteVibratorOff(eq(UID));
|
||||||
verify(mControllerCallbacks).onComplete(eq(1), eq(vibrationId));
|
verify(mControllerCallbacks).onComplete(eq(1), eq(vibrationId));
|
||||||
verify(mControllerCallbacks).onComplete(eq(2), eq(vibrationId));
|
verify(mControllerCallbacks).onComplete(eq(2), eq(vibrationId));
|
||||||
verify(mControllerCallbacks).onComplete(eq(3), eq(vibrationId));
|
verify(mControllerCallbacks).onComplete(eq(3), eq(vibrationId));
|
||||||
@@ -1300,7 +1296,7 @@ public class VibrationThreadTest {
|
|||||||
|
|
||||||
private VibrationThread startThreadAndDispatcher(Vibration vib) {
|
private VibrationThread startThreadAndDispatcher(Vibration vib) {
|
||||||
VibrationThread thread = new VibrationThread(vib, mVibrationSettings, mEffectAdapter,
|
VibrationThread thread = new VibrationThread(vib, mVibrationSettings, mEffectAdapter,
|
||||||
createVibratorControllers(), mWakeLock, mIBatteryStatsMock, mManagerHooks);
|
createVibratorControllers(), mWakeLock, mManagerHooks);
|
||||||
doAnswer(answer -> {
|
doAnswer(answer -> {
|
||||||
thread.vibratorComplete(answer.getArgument(0));
|
thread.vibratorComplete(answer.getArgument(0));
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
@@ -71,6 +71,7 @@ import android.os.VibratorInfo;
|
|||||||
import android.os.test.TestLooper;
|
import android.os.test.TestLooper;
|
||||||
import android.os.vibrator.PrebakedSegment;
|
import android.os.vibrator.PrebakedSegment;
|
||||||
import android.os.vibrator.PrimitiveSegment;
|
import android.os.vibrator.PrimitiveSegment;
|
||||||
|
import android.os.vibrator.VibrationConfig;
|
||||||
import android.os.vibrator.VibrationEffectSegment;
|
import android.os.vibrator.VibrationEffectSegment;
|
||||||
import android.platform.test.annotations.Presubmit;
|
import android.platform.test.annotations.Presubmit;
|
||||||
import android.provider.Settings;
|
import android.provider.Settings;
|
||||||
@@ -78,6 +79,7 @@ import android.view.InputDevice;
|
|||||||
|
|
||||||
import androidx.test.InstrumentationRegistry;
|
import androidx.test.InstrumentationRegistry;
|
||||||
|
|
||||||
|
import com.android.internal.app.IBatteryStats;
|
||||||
import com.android.internal.util.test.FakeSettingsProvider;
|
import com.android.internal.util.test.FakeSettingsProvider;
|
||||||
import com.android.internal.util.test.FakeSettingsProviderRule;
|
import com.android.internal.util.test.FakeSettingsProviderRule;
|
||||||
import com.android.server.LocalServices;
|
import com.android.server.LocalServices;
|
||||||
@@ -144,6 +146,8 @@ public class VibratorManagerServiceTest {
|
|||||||
private AppOpsManager mAppOpsManagerMock;
|
private AppOpsManager mAppOpsManagerMock;
|
||||||
@Mock
|
@Mock
|
||||||
private IInputManager mIInputManagerMock;
|
private IInputManager mIInputManagerMock;
|
||||||
|
@Mock
|
||||||
|
private IBatteryStats mBatteryStatsMock;
|
||||||
|
|
||||||
private final Map<Integer, FakeVibratorControllerProvider> mVibratorProviders = new HashMap<>();
|
private final Map<Integer, FakeVibratorControllerProvider> mVibratorProviders = new HashMap<>();
|
||||||
|
|
||||||
@@ -152,12 +156,14 @@ public class VibratorManagerServiceTest {
|
|||||||
private FakeVibrator mVibrator;
|
private FakeVibrator mVibrator;
|
||||||
private PowerManagerInternal.LowPowerModeListener mRegisteredPowerModeListener;
|
private PowerManagerInternal.LowPowerModeListener mRegisteredPowerModeListener;
|
||||||
private VibratorManagerService.ExternalVibratorService mExternalVibratorService;
|
private VibratorManagerService.ExternalVibratorService mExternalVibratorService;
|
||||||
|
private VibrationConfig mVibrationConfig;
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
public void setUp() throws Exception {
|
public void setUp() throws Exception {
|
||||||
mTestLooper = new TestLooper();
|
mTestLooper = new TestLooper();
|
||||||
mContextSpy = spy(new ContextWrapper(InstrumentationRegistry.getContext()));
|
mContextSpy = spy(new ContextWrapper(InstrumentationRegistry.getContext()));
|
||||||
InputManager inputManager = InputManager.resetInstance(mIInputManagerMock);
|
InputManager inputManager = InputManager.resetInstance(mIInputManagerMock);
|
||||||
|
mVibrationConfig = new VibrationConfig(mContextSpy.getResources());
|
||||||
|
|
||||||
ContentResolver contentResolver = mSettingsProviderRule.mockContentResolver(mContextSpy);
|
ContentResolver contentResolver = mSettingsProviderRule.mockContentResolver(mContextSpy);
|
||||||
when(mContextSpy.getContentResolver()).thenReturn(contentResolver);
|
when(mContextSpy.getContentResolver()).thenReturn(contentResolver);
|
||||||
@@ -221,6 +227,11 @@ public class VibratorManagerServiceTest {
|
|||||||
return new Handler(mTestLooper.getLooper());
|
return new Handler(mTestLooper.getLooper());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
IBatteryStats getBatteryStatsService() {
|
||||||
|
return mBatteryStatsMock;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
VibratorController createVibratorController(int vibratorId,
|
VibratorController createVibratorController(int vibratorId,
|
||||||
VibratorController.OnVibrationCompleteListener listener) {
|
VibratorController.OnVibrationCompleteListener listener) {
|
||||||
@@ -382,6 +393,11 @@ public class VibratorManagerServiceTest {
|
|||||||
inOrderVerifier.verify(listenerMock).onVibrating(eq(true));
|
inOrderVerifier.verify(listenerMock).onVibrating(eq(true));
|
||||||
inOrderVerifier.verify(listenerMock).onVibrating(eq(false));
|
inOrderVerifier.verify(listenerMock).onVibrating(eq(false));
|
||||||
inOrderVerifier.verifyNoMoreInteractions();
|
inOrderVerifier.verifyNoMoreInteractions();
|
||||||
|
|
||||||
|
InOrder batteryVerifier = inOrder(mBatteryStatsMock);
|
||||||
|
batteryVerifier.verify(mBatteryStatsMock)
|
||||||
|
.noteVibratorOn(UID, 40 + mVibrationConfig.getRampDownDurationMs());
|
||||||
|
batteryVerifier.verify(mBatteryStatsMock).noteVibratorOff(UID);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -731,6 +747,12 @@ public class VibratorManagerServiceTest {
|
|||||||
// Wait before checking it never played a second effect.
|
// Wait before checking it never played a second effect.
|
||||||
assertFalse(waitUntil(s -> mVibratorProviders.get(1).getEffectSegments().size() > 1,
|
assertFalse(waitUntil(s -> mVibratorProviders.get(1).getEffectSegments().size() > 1,
|
||||||
service, /* timeout= */ 50));
|
service, /* timeout= */ 50));
|
||||||
|
|
||||||
|
// The time estimate is recorded when the vibration starts, repeating vibrations
|
||||||
|
// are capped at BATTERY_STATS_REPEATING_VIBRATION_DURATION (=5000).
|
||||||
|
verify(mBatteryStatsMock).noteVibratorOn(UID, 5000);
|
||||||
|
// The second vibration shouldn't have recorded that the vibrators were turned on.
|
||||||
|
verify(mBatteryStatsMock, times(1)).noteVibratorOn(anyInt(), anyLong());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|||||||
Reference in New Issue
Block a user