1 /* 2 * Copyright (C) 2022 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.server.vibrator; 18 19 import android.annotation.NonNull; 20 import android.os.SystemClock; 21 import android.os.Trace; 22 import android.os.VibrationEffect; 23 import android.os.vibrator.Flags; 24 import android.os.vibrator.StepSegment; 25 import android.os.vibrator.VibrationEffectSegment; 26 import android.util.Slog; 27 28 import java.util.Arrays; 29 import java.util.List; 30 31 /** 32 * Represents a step to turn the vibrator on and change its amplitude. 33 * 34 * <p>This step ignores vibration completion callbacks and control the vibrator on/off state 35 * and amplitude to simulate waveforms represented by a sequence of {@link StepSegment}. 36 */ 37 final class SetAmplitudeVibratorStep extends AbstractComposedVibratorStep { 38 /** 39 * The repeating waveform keeps the vibrator ON all the time. Use a minimum duration to 40 * prevent short patterns from turning the vibrator ON too frequently. 41 */ 42 static final int REPEATING_EFFECT_ON_DURATION = 5000; // 5s 43 SetAmplitudeVibratorStep(VibrationStepConductor conductor, long startTime, VibratorController controller, VibrationEffect.Composed effect, int index, long pendingVibratorOffDeadline)44 SetAmplitudeVibratorStep(VibrationStepConductor conductor, long startTime, 45 VibratorController controller, VibrationEffect.Composed effect, int index, 46 long pendingVibratorOffDeadline) { 47 // This step has a fixed startTime coming from the timings of the waveform it's playing. 48 super(conductor, startTime, controller, effect, index, pendingVibratorOffDeadline); 49 } 50 51 @Override acceptVibratorCompleteCallback(int vibratorId)52 public boolean acceptVibratorCompleteCallback(int vibratorId) { 53 if (Flags.fixVibrationThreadCallbackHandling()) { 54 // TODO: remove this method once flag removed. 55 return super.acceptVibratorCompleteCallback(vibratorId); 56 } 57 // Ensure the super method is called and will reset the off timeout and boolean flag. 58 // This is true if the vibrator was ON and this callback has the same vibratorId. 59 if (!super.acceptVibratorCompleteCallback(vibratorId)) { 60 return false; 61 } 62 63 // Timings are tightly controlled here, so only trigger this step if the vibrator was 64 // supposed to be ON but has completed prematurely, to turn it back on as soon as 65 // possible. If the vibrator turned off during a zero-amplitude step, just wait for 66 // the correct start time of this step before playing it. 67 boolean shouldAcceptCallback = 68 (SystemClock.uptimeMillis() < startTime) && (controller.getCurrentAmplitude() > 0); 69 70 if (VibrationThread.DEBUG) { 71 Slog.d(VibrationThread.TAG, 72 "Amplitude step received completion callback from " + vibratorId 73 + ", accepted = " + shouldAcceptCallback); 74 } 75 return shouldAcceptCallback; 76 } 77 78 @NonNull 79 @Override play()80 public List<Step> play() { 81 // TODO: consider separating the "on" steps at the start into a separate Step. 82 // TODO: consider instantiating the step with the required amplitude, rather than 83 // needing to dig into the effect. 84 Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "SetAmplitudeVibratorStep"); 85 try { 86 long now = SystemClock.uptimeMillis(); 87 long latency = now - startTime; 88 if (VibrationThread.DEBUG) { 89 Slog.d(VibrationThread.TAG, 90 "Running amplitude step with " + latency + "ms latency."); 91 } 92 93 if (mVibratorCompleteCallbackReceived && latency < 0) { 94 // This step was run early because the vibrator turned off prematurely. 95 // Turn it back on and return this same step to run at the exact right time. 96 turnVibratorBackOn(/* remainingDuration= */ -latency); 97 return Arrays.asList(new SetAmplitudeVibratorStep(conductor, startTime, controller, 98 effect, segmentIndex, mPendingVibratorOffDeadline)); 99 } 100 101 VibrationEffectSegment segment = effect.getSegments().get(segmentIndex); 102 if (!(segment instanceof StepSegment)) { 103 Slog.w(VibrationThread.TAG, 104 "Ignoring wrong segment for a SetAmplitudeVibratorStep: " + segment); 105 // Use original startTime to avoid propagating latencies to the waveform. 106 return nextSteps(startTime, /* segmentsPlayed= */ 1); 107 } 108 109 StepSegment stepSegment = (StepSegment) segment; 110 if (stepSegment.getDuration() == 0) { 111 // Use original startTime to avoid propagating latencies to the waveform. 112 return nextSteps(startTime, /* segmentsPlayed= */ 1); 113 } 114 115 float amplitude = stepSegment.getAmplitude(); 116 if (amplitude == 0) { 117 if (mPendingVibratorOffDeadline > now) { 118 // Amplitude cannot be set to zero, so stop the vibrator. 119 stopVibrating(); 120 } 121 } else { 122 if (startTime >= mPendingVibratorOffDeadline) { 123 // Vibrator is OFF. Turn vibrator back on for the duration of another 124 // cycle before setting the amplitude. 125 long onDuration = getVibratorOnDuration(effect, segmentIndex); 126 if (onDuration > 0) { 127 startVibrating(onDuration); 128 } 129 } 130 changeAmplitude(amplitude); 131 } 132 133 // Use original startTime to avoid propagating latencies to the waveform. 134 long nextStartTime = startTime + segment.getDuration(); 135 return nextSteps(nextStartTime, /* segmentsPlayed= */ 1); 136 } finally { 137 Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR); 138 } 139 } 140 turnVibratorBackOn(long remainingDuration)141 private void turnVibratorBackOn(long remainingDuration) { 142 long onDuration = getVibratorOnDuration(effect, segmentIndex); 143 if (onDuration <= 0) { 144 // Vibrator is supposed to go back off when this step starts, so just leave it off. 145 return; 146 } 147 onDuration += remainingDuration; 148 149 if (VibrationThread.DEBUG) { 150 Slog.d(VibrationThread.TAG, 151 "Turning the vibrator back ON using the remaining duration of " 152 + remainingDuration + "ms, for a total of " + onDuration + "ms"); 153 } 154 155 float expectedAmplitude = controller.getCurrentAmplitude(); 156 long vibratorOnResult = startVibrating(onDuration); 157 if (vibratorOnResult > 0) { 158 // Set the amplitude back to the value it was supposed to be playing at. 159 changeAmplitude(expectedAmplitude); 160 } 161 } 162 startVibrating(long duration)163 private long startVibrating(long duration) { 164 if (VibrationThread.DEBUG) { 165 Slog.d(VibrationThread.TAG, 166 "Turning on vibrator " + controller.getVibratorInfo().getId() + " for " 167 + duration + "ms"); 168 } 169 int stepId = conductor.nextVibratorCallbackStepId(getVibratorId()); 170 long vibratorOnResult = controller.on(duration, getVibration().id, stepId); 171 handleVibratorOnResult(vibratorOnResult); 172 getVibration().stats.reportVibratorOn(vibratorOnResult); 173 return vibratorOnResult; 174 } 175 176 /** 177 * Get the duration the vibrator will be on for a waveform, starting at {@code startIndex} 178 * until the next time it's vibrating amplitude is zero or a different type of segment is 179 * found. 180 */ getVibratorOnDuration(VibrationEffect.Composed effect, int startIndex)181 private long getVibratorOnDuration(VibrationEffect.Composed effect, int startIndex) { 182 List<VibrationEffectSegment> segments = effect.getSegments(); 183 int segmentCount = segments.size(); 184 int repeatIndex = effect.getRepeatIndex(); 185 int i = startIndex; 186 long timing = 0; 187 while (i < segmentCount) { 188 VibrationEffectSegment segment = segments.get(i); 189 if (!(segment instanceof StepSegment) 190 // play() will ignore segments with zero duration, so it's important that 191 // zero-duration segments don't affect this method. 192 || (segment.getDuration() > 0 && ((StepSegment) segment).getAmplitude() == 0)) { 193 break; 194 } 195 timing += segment.getDuration(); 196 i++; 197 if (i == segmentCount && repeatIndex >= 0) { 198 i = repeatIndex; 199 // prevent infinite loop 200 repeatIndex = -1; 201 } 202 if (i == startIndex) { 203 return Math.max(timing, REPEATING_EFFECT_ON_DURATION); 204 } 205 } 206 if (i == segmentCount && effect.getRepeatIndex() < 0) { 207 // Vibration ending at non-zero amplitude, add extra timings to ramp down after 208 // vibration is complete. 209 timing += conductor.vibrationSettings.getRampDownDuration(); 210 } 211 return timing; 212 } 213 } 214