• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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