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.annotation.Nullable;
21 import android.os.Build;
22 import android.os.CombinedVibration;
23 import android.os.IBinder;
24 import android.os.VibrationAttributes;
25 import android.os.VibrationEffect;
26 import android.os.vibrator.Flags;
27 import android.os.vibrator.PrebakedSegment;
28 import android.os.vibrator.PrimitiveSegment;
29 import android.os.vibrator.RampSegment;
30 import android.os.vibrator.VibrationEffectSegment;
31 import android.util.IntArray;
32 import android.util.Slog;
33 import android.util.SparseArray;
34 
35 import com.android.internal.annotations.GuardedBy;
36 
37 import java.util.ArrayList;
38 import java.util.Iterator;
39 import java.util.LinkedList;
40 import java.util.List;
41 import java.util.PriorityQueue;
42 import java.util.Queue;
43 import java.util.concurrent.CancellationException;
44 import java.util.concurrent.CompletableFuture;
45 import java.util.concurrent.TimeUnit;
46 import java.util.concurrent.TimeoutException;
47 
48 /**
49  * Creates and manages a queue of steps for performing a VibrationEffect, as well as coordinating
50  * dispatch of callbacks.
51  *
52  * <p>In general, methods in this class are intended to be called only by a single instance of
53  * VibrationThread. The only thread-safe methods for calling from other threads are the "notify"
54  * methods (which should never be used from the VibrationThread thread).
55  */
56 final class VibrationStepConductor implements IBinder.DeathRecipient {
57     private static final boolean DEBUG = VibrationThread.DEBUG;
58     private static final String TAG = VibrationThread.TAG;
59 
60     /**
61      * Extra timeout added to the end of each vibration step to ensure it finishes even when
62      * vibrator callbacks are lost.
63      */
64     static final long CALLBACKS_EXTRA_TIMEOUT = 1_000;
65     /** Threshold to prevent the ramp off steps from trying to set extremely low amplitudes. */
66     static final float RAMP_OFF_AMPLITUDE_MIN = 1e-3f;
67     static final List<Step> EMPTY_STEP_LIST = new ArrayList<>();
68 
69     // Used within steps.
70     public final VibrationSettings vibrationSettings;
71     public final VibrationThread.VibratorManagerHooks vibratorManagerHooks;
72 
73     private final DeviceAdapter mDeviceAdapter;
74     private final VibrationScaler mVibrationScaler;
75     private final VibratorFrameworkStatsLogger mStatsLogger;
76 
77     // Not guarded by lock because it's mostly used to read immutable fields by this conductor.
78     // This is only modified here at the prepareToStart method which always runs at the vibration
79     // thread, to update the adapted effect and report start time.
80     private final HalVibration mVibration;
81     private final PriorityQueue<Step> mNextSteps = new PriorityQueue<>();
82     private final Queue<Step> mPendingOnVibratorCompleteSteps = new LinkedList<>();
83 
84     @Nullable
85     private final CompletableFuture<Void> mRequestVibrationParamsFuture;
86 
87     // Signalling fields.
88     // Note that vibrator callback signals may happen inside vibrator HAL calls made by the
89     // VibrationThread, or on an external executor, so this lock should not be held for anything
90     // other than updating signalling state - particularly not during HAL calls or when invoking
91     // other callbacks that may trigger calls into the thread.
92     private final Object mLock = new Object();
93     @GuardedBy("mLock")
94     private final IntArray mSignalVibratorsComplete;
95     @Nullable
96     @GuardedBy("mLock")
97     private Vibration.EndInfo mSignalCancel = null;
98     @GuardedBy("mLock")
99     private boolean mSignalCancelImmediate = false;
100 
101     @Nullable
102     private Vibration.EndInfo mCancelledVibrationEndInfo = null;
103     private boolean mCancelledImmediately = false;  // hard stop
104     private int mPendingVibrateSteps;
105     private int mRemainingStartSequentialEffectSteps;
106     private int mSuccessfulVibratorOnSteps;
107 
VibrationStepConductor(HalVibration vib, VibrationSettings vibrationSettings, DeviceAdapter deviceAdapter, VibrationScaler vibrationScaler, VibratorFrameworkStatsLogger statsLogger, CompletableFuture<Void> requestVibrationParamsFuture, VibrationThread.VibratorManagerHooks vibratorManagerHooks)108     VibrationStepConductor(HalVibration vib, VibrationSettings vibrationSettings,
109             DeviceAdapter deviceAdapter, VibrationScaler vibrationScaler,
110             VibratorFrameworkStatsLogger statsLogger,
111             CompletableFuture<Void> requestVibrationParamsFuture,
112             VibrationThread.VibratorManagerHooks vibratorManagerHooks) {
113         this.mVibration = vib;
114         this.vibrationSettings = vibrationSettings;
115         this.mDeviceAdapter = deviceAdapter;
116         mVibrationScaler = vibrationScaler;
117         mStatsLogger = statsLogger;
118         mRequestVibrationParamsFuture = requestVibrationParamsFuture;
119         this.vibratorManagerHooks = vibratorManagerHooks;
120         this.mSignalVibratorsComplete =
121                 new IntArray(mDeviceAdapter.getAvailableVibratorIds().length);
122     }
123 
124     @Nullable
nextVibrateStep(long startTime, VibratorController controller, VibrationEffect.Composed effect, int segmentIndex, long pendingVibratorOffDeadline)125     AbstractVibratorStep nextVibrateStep(long startTime, VibratorController controller,
126             VibrationEffect.Composed effect, int segmentIndex, long pendingVibratorOffDeadline) {
127         if (Build.IS_DEBUGGABLE) {
128             expectIsVibrationThread(true);
129         }
130         if (segmentIndex >= effect.getSegments().size()) {
131             segmentIndex = effect.getRepeatIndex();
132         }
133         if (segmentIndex < 0) {
134             // No more segments to play, last step is to complete the vibration on this vibrator.
135             return new CompleteEffectVibratorStep(this, startTime, /* cancelled= */ false,
136                     controller, pendingVibratorOffDeadline);
137         }
138 
139         VibrationEffectSegment segment = effect.getSegments().get(segmentIndex);
140         if (segment instanceof PrebakedSegment) {
141             return new PerformPrebakedVibratorStep(this, startTime, controller, effect,
142                     segmentIndex, pendingVibratorOffDeadline);
143         }
144         if (segment instanceof PrimitiveSegment) {
145             return new ComposePrimitivesVibratorStep(this, startTime, controller, effect,
146                     segmentIndex, pendingVibratorOffDeadline);
147         }
148         if (segment instanceof RampSegment) {
149             return new ComposePwleVibratorStep(this, startTime, controller, effect, segmentIndex,
150                     pendingVibratorOffDeadline);
151         }
152         return new SetAmplitudeVibratorStep(this, startTime, controller, effect, segmentIndex,
153                 pendingVibratorOffDeadline);
154     }
155 
156     /** Called when this conductor is going to be started running by the VibrationThread. */
prepareToStart()157     public void prepareToStart() {
158         if (Build.IS_DEBUGGABLE) {
159             expectIsVibrationThread(true);
160         }
161 
162         if (!mVibration.callerInfo.attrs.isFlagSet(
163                 VibrationAttributes.FLAG_BYPASS_USER_VIBRATION_INTENSITY_SCALE)) {
164             if (Flags.adaptiveHapticsEnabled()) {
165                 waitForVibrationParamsIfRequired();
166             }
167             // Scale resolves the default amplitudes from the effect before scaling them.
168             mVibration.scaleEffects(mVibrationScaler);
169         } else {
170             mVibration.resolveEffects(mVibrationScaler.getDefaultVibrationAmplitude());
171         }
172 
173         mVibration.adaptToDevice(mDeviceAdapter);
174         CombinedVibration.Sequential sequentialEffect = toSequential(mVibration.getEffectToPlay());
175         mPendingVibrateSteps++;
176         // This count is decremented at the completion of the step, so we don't subtract one.
177         mRemainingStartSequentialEffectSteps = sequentialEffect.getEffects().size();
178         mNextSteps.offer(new StartSequentialEffectStep(this, sequentialEffect));
179         // Vibration will start playing in the Vibrator, following the effect timings and delays.
180         // Report current time as the vibration start time, for debugging.
181         mVibration.stats.reportStarted();
182     }
183 
getVibration()184     public HalVibration getVibration() {
185         // No thread assertion: immutable
186         return mVibration;
187     }
188 
getVibrators()189     SparseArray<VibratorController> getVibrators() {
190         // No thread assertion: immutable
191         return mDeviceAdapter.getAvailableVibrators();
192     }
193 
isFinished()194     public boolean isFinished() {
195         if (Build.IS_DEBUGGABLE) {
196             expectIsVibrationThread(true);
197         }
198         if (mCancelledImmediately) {
199             return true;  // Terminate.
200         }
201 
202         // No need to check for vibration complete callbacks - if there were any, they would
203         // have no steps to notify anyway.
204         return mPendingOnVibratorCompleteSteps.isEmpty() && mNextSteps.isEmpty();
205     }
206 
207     /**
208      * Calculate the {@link Vibration.Status} based on the current queue state and the expected
209      * number of {@link StartSequentialEffectStep} to be played.
210      */
211     @Nullable
calculateVibrationEndInfo()212     public Vibration.EndInfo calculateVibrationEndInfo() {
213         if (Build.IS_DEBUGGABLE) {
214             expectIsVibrationThread(true);
215         }
216 
217         if (mCancelledVibrationEndInfo != null) {
218             return mCancelledVibrationEndInfo;
219         }
220         if (mPendingVibrateSteps > 0 || mRemainingStartSequentialEffectSteps > 0) {
221             // Vibration still running.
222             return null;
223         }
224         // No pending steps, and something happened.
225         if (mSuccessfulVibratorOnSteps > 0) {
226             return new Vibration.EndInfo(Vibration.Status.FINISHED);
227         }
228         // If no step was able to turn the vibrator ON successfully.
229         return new Vibration.EndInfo(Vibration.Status.IGNORED_UNSUPPORTED);
230     }
231 
232     /**
233      * Blocks until the next step is due to run. The wait here may be interrupted by calling
234      * one of the "notify" methods.
235      *
236      * <p>This method returns true if the next step is ready to run now. If the method returns
237      * false, then some waiting was done, but may have been interrupted by a wakeUp, and the
238      * status and isFinished of the vibration should be re-checked before calling this method again.
239      *
240      * @return true if the next step can be run now or the vibration is finished, or false if this
241      *   method waited and the conductor state may have changed asynchronously, in which case this
242      *   method needs to be run again.
243      */
waitUntilNextStepIsDue()244     public boolean waitUntilNextStepIsDue() {
245         if (Build.IS_DEBUGGABLE) {
246             expectIsVibrationThread(true);
247         }
248 
249         processAllNotifySignals();
250         if (mCancelledImmediately) {
251             // Don't try to run a step for immediate cancel, although there should be none left.
252             // Non-immediate cancellation may have cleanup steps, so it continues processing.
253             return false;
254         }
255         if (!mPendingOnVibratorCompleteSteps.isEmpty()) {
256             return true;  // Resumed step ready.
257         }
258         Step nextStep = mNextSteps.peek();
259         if (nextStep == null) {
260             return true;  // Finished
261         }
262         long waitMillis = nextStep.calculateWaitTime();
263         if (waitMillis <= 0) {
264             return true;  // Regular step ready
265         }
266         synchronized (mLock) {
267             // Double check for signals before sleeping, as their notify wouldn't interrupt a fresh
268             // wait.
269             if (hasPendingNotifySignalLocked()) {
270                 // Don't run the next step, it will loop back to this method and process them.
271                 return false;
272             }
273             try {
274                 mLock.wait(waitMillis);
275             } catch (InterruptedException e) {
276             }
277             return false;  // Caller needs to check isFinished and maybe wait again.
278         }
279     }
280 
281     @Nullable
pollNext()282     private Step pollNext() {
283         if (Build.IS_DEBUGGABLE) {
284             expectIsVibrationThread(true);
285         }
286 
287         // Prioritize the steps resumed by a vibrator complete callback, irrespective of their
288         // "next run time".
289         if (!mPendingOnVibratorCompleteSteps.isEmpty()) {
290             return mPendingOnVibratorCompleteSteps.poll();
291         }
292         return mNextSteps.poll();
293     }
294 
295     /**
296      * Play and remove the step at the top of this queue, and also adds the next steps generated
297      * to be played next.
298      */
runNextStep()299     public void runNextStep() {
300         if (Build.IS_DEBUGGABLE) {
301             expectIsVibrationThread(true);
302         }
303         // In theory a completion callback could have come in between the wait finishing and
304         // this method starting, but that only means the step is due now anyway, so it's reasonable
305         // to run it before processing callbacks as the window is tiny.
306         Step nextStep = pollNext();
307         if (nextStep != null) {
308             if (DEBUG) {
309                 Slog.d(TAG, "Playing vibration id " + getVibration().id
310                         + ((nextStep instanceof AbstractVibratorStep)
311                         ? " on vibrator " + ((AbstractVibratorStep) nextStep).getVibratorId() : "")
312                         + " " + nextStep.getClass().getSimpleName()
313                         + (nextStep.isCleanUp() ? " (cleanup)" : ""));
314             }
315 
316             List<Step> nextSteps = nextStep.play();
317             if (nextStep.getVibratorOnDuration() > 0) {
318                 mSuccessfulVibratorOnSteps++;
319             }
320             if (nextStep instanceof StartSequentialEffectStep) {
321                 mRemainingStartSequentialEffectSteps--;
322             }
323             if (!nextStep.isCleanUp()) {
324                 mPendingVibrateSteps--;
325             }
326             for (int i = 0; i < nextSteps.size(); i++) {
327                 mPendingVibrateSteps += nextSteps.get(i).isCleanUp() ? 0 : 1;
328             }
329             mNextSteps.addAll(nextSteps);
330         }
331     }
332 
333     /**
334      * Binder death notification. VibrationThread registers this when it's running a conductor.
335      * Note that cancellation could theoretically happen immediately, before the conductor has
336      * started, but in this case it will be processed in the first signals loop.
337      */
338     @Override
binderDied()339     public void binderDied() {
340         if (DEBUG) {
341             Slog.d(TAG, "Binder died, cancelling vibration...");
342         }
343         notifyCancelled(new Vibration.EndInfo(Vibration.Status.CANCELLED_BINDER_DIED),
344                 /* immediate= */ false);
345     }
346 
347     /**
348      * Notify the execution that cancellation is requested. This will be acted upon
349      * asynchronously in the VibrationThread.
350      *
351      * <p>Only the first cancel signal will be used to end a cancelled vibration, but subsequent
352      * calls with {@code immediate} flag set to true can still force the first cancel signal to
353      * take effect urgently.
354      *
355      * @param immediate indicates whether cancellation should abort urgently and skip cleanup steps.
356      */
notifyCancelled(@onNull Vibration.EndInfo cancelInfo, boolean immediate)357     public void notifyCancelled(@NonNull Vibration.EndInfo cancelInfo, boolean immediate) {
358         if (Build.IS_DEBUGGABLE) {
359             expectIsVibrationThread(false);
360         }
361         if (DEBUG) {
362             Slog.d(TAG, "Vibration cancel requested with signal=" + cancelInfo
363                     + ", immediate=" + immediate);
364         }
365         if ((cancelInfo == null) || !cancelInfo.status.name().startsWith("CANCEL")) {
366             Slog.w(TAG, "Vibration cancel requested with bad signal=" + cancelInfo
367                     + ", using CANCELLED_UNKNOWN_REASON to ensure cancellation.");
368             cancelInfo = new Vibration.EndInfo(Vibration.Status.CANCELLED_BY_UNKNOWN_REASON);
369         }
370         synchronized (mLock) {
371             if ((immediate && mSignalCancelImmediate) || (mSignalCancel != null)) {
372                 if (DEBUG) {
373                     Slog.d(TAG, "Vibration cancel request ignored as the vibration "
374                             + mVibration.id + "is already being cancelled with signal="
375                             + mSignalCancel + ", immediate=" + mSignalCancelImmediate);
376                 }
377                 return;
378             }
379             mSignalCancelImmediate |= immediate;
380             if (mSignalCancel == null) {
381                 mSignalCancel = cancelInfo;
382             } else {
383                 if (DEBUG) {
384                     Slog.d(TAG, "Vibration cancel request new signal=" + cancelInfo
385                             + " ignored as the vibration was already cancelled with signal="
386                             + mSignalCancel + ", but immediate flag was updated to "
387                             + mSignalCancelImmediate);
388                 }
389             }
390             if (mRequestVibrationParamsFuture != null) {
391                 mRequestVibrationParamsFuture.cancel(/* mayInterruptIfRunning= */true);
392             }
393             mLock.notify();
394         }
395     }
396 
397     /**
398      * Notify the conductor that a vibrator has completed its work.
399      *
400      * <p>This is a lightweight method intended to be called directly via native callbacks.
401      * The state update is recorded for processing on the main execution thread (VibrationThread).
402      */
notifyVibratorComplete(int vibratorId)403     public void notifyVibratorComplete(int vibratorId) {
404         // HAL callbacks may be triggered directly within HAL calls, so these notifications
405         // could be on the VibrationThread as it calls the HAL, or some other executor later.
406         // Therefore no thread assertion is made here.
407 
408         if (DEBUG) {
409             Slog.d(TAG, "Vibration complete reported by vibrator " + vibratorId);
410         }
411 
412         synchronized (mLock) {
413             mSignalVibratorsComplete.add(vibratorId);
414             mLock.notify();
415         }
416     }
417 
418     /**
419      * Notify that a VibratorManager sync operation has completed.
420      *
421      * <p>This is a lightweight method intended to be called directly via native callbacks.
422      * The state update is recorded for processing on the main execution thread
423      * (VibrationThread).
424      */
notifySyncedVibrationComplete()425     public void notifySyncedVibrationComplete() {
426         // HAL callbacks may be triggered directly within HAL calls, so these notifications
427         // could be on the VibrationThread as it calls the HAL, or some other executor later.
428         // Therefore no thread assertion is made here.
429 
430         if (DEBUG) {
431             Slog.d(TAG, "Synced vibration complete reported by vibrator manager");
432         }
433 
434         synchronized (mLock) {
435             for (int vibratorId : mDeviceAdapter.getAvailableVibratorIds()) {
436                 mSignalVibratorsComplete.add(vibratorId);
437             }
438             mLock.notify();
439         }
440     }
441 
442     /** Returns true if a cancellation signal was sent via {@link #notifyCancelled}. */
wasNotifiedToCancel()443     public boolean wasNotifiedToCancel() {
444         if (Build.IS_DEBUGGABLE) {
445             expectIsVibrationThread(false);
446         }
447         synchronized (mLock) {
448             return mSignalCancel != null;
449         }
450     }
451 
452     /**
453      * Blocks until the vibration params future is complete.
454      *
455      * This should be called by the VibrationThread and may be interrupted by calling
456      * `notifyCancelled` from outside it.
457      */
waitForVibrationParamsIfRequired()458     private void waitForVibrationParamsIfRequired() {
459         if (Build.IS_DEBUGGABLE) {
460             expectIsVibrationThread(true);
461         }
462 
463         if (mRequestVibrationParamsFuture == null) {
464             return;
465         }
466 
467         try {
468             mRequestVibrationParamsFuture.get(
469                     vibrationSettings.getRequestVibrationParamsTimeoutMs(),
470                     TimeUnit.MILLISECONDS);
471         } catch (TimeoutException e) {
472             if (DEBUG) {
473                 Slog.d(TAG, "Request for vibration params timed out", e);
474             }
475             mStatsLogger.logVibrationParamRequestTimeout(mVibration.callerInfo.uid);
476         } catch (CancellationException e) {
477             if (DEBUG) {
478                 Slog.d(TAG, "Request for vibration params cancelled, maybe superseded or"
479                         + " vibrator controller unregistered. Skipping params...", e);
480             }
481         } catch (Throwable e) {
482             Slog.w(TAG, "Failed to retrieve vibration params.", e);
483         }
484     }
485 
486     @GuardedBy("mLock")
hasPendingNotifySignalLocked()487     private boolean hasPendingNotifySignalLocked() {
488         if (Build.IS_DEBUGGABLE) {
489             expectIsVibrationThread(true);  // Reads VibrationThread variables as well as signals.
490         }
491         return (mSignalCancel != null && mCancelledVibrationEndInfo == null)
492                 || (mSignalCancelImmediate && !mCancelledImmediately)
493                 || (mSignalVibratorsComplete.size() > 0);
494     }
495 
496     /**
497      * Process any notified cross-thread signals, applying the necessary VibrationThread state
498      * changes.
499      */
processAllNotifySignals()500     private void processAllNotifySignals() {
501         if (Build.IS_DEBUGGABLE) {
502             expectIsVibrationThread(true);
503         }
504 
505         int[] vibratorsToProcess = null;
506         Vibration.EndInfo doCancelInfo = null;
507         boolean doCancelImmediate = false;
508         // Collect signals to process, but don't keep the lock while processing them.
509         synchronized (mLock) {
510             if (mSignalCancelImmediate) {
511                 if (mCancelledImmediately) {
512                     Slog.wtf(TAG, "Immediate cancellation signal processed twice");
513                 }
514                 // This should only happen once.
515                 doCancelImmediate = true;
516                 doCancelInfo = mSignalCancel;
517             }
518             if ((mSignalCancel != null) && (mCancelledVibrationEndInfo == null)) {
519                 doCancelInfo = mSignalCancel;
520             }
521             if (!doCancelImmediate && mSignalVibratorsComplete.size() > 0) {
522                 // Swap out the queue of completions to process.
523                 vibratorsToProcess = mSignalVibratorsComplete.toArray();  // makes a copy
524                 mSignalVibratorsComplete.clear();
525             }
526         }
527 
528         // Force cancellation means stop everything and clear all steps, so the execution loop
529         // shouldn't come back to this method. To observe explicitly: this drops vibrator
530         // completion signals that were collected in this call, but we won't process them
531         // anyway as all steps are cancelled.
532         if (doCancelImmediate) {
533             processCancelImmediately(doCancelInfo);
534             return;
535         }
536         if (doCancelInfo != null) {
537             processCancel(doCancelInfo);
538         }
539         if (vibratorsToProcess != null) {
540             processVibratorsComplete(vibratorsToProcess);
541         }
542     }
543 
544     /**
545      * Cancel the current queue, replacing all remaining steps with respective clean-up steps.
546      *
547      * <p>This will remove all steps and replace them with respective results of
548      * {@link Step#cancel()}.
549      */
processCancel(Vibration.EndInfo cancelInfo)550     public void processCancel(Vibration.EndInfo cancelInfo) {
551         if (Build.IS_DEBUGGABLE) {
552             expectIsVibrationThread(true);
553         }
554 
555         mCancelledVibrationEndInfo = cancelInfo;
556         // Vibrator callbacks should wait until all steps from the queue are properly cancelled
557         // and clean up steps are added back to the queue, so they can handle the callback.
558         List<Step> cleanUpSteps = new ArrayList<>();
559         Step step;
560         while ((step = pollNext()) != null) {
561             cleanUpSteps.addAll(step.cancel());
562         }
563         // All steps generated by Step.cancel() should be clean-up steps.
564         mPendingVibrateSteps = 0;
565         mNextSteps.addAll(cleanUpSteps);
566     }
567 
568     /**
569      * Cancel the current queue immediately, clearing all remaining steps and skipping clean-up.
570      *
571      * <p>This will remove and trigger {@link Step#cancelImmediately()} in all steps, in order.
572      */
processCancelImmediately(Vibration.EndInfo cancelInfo)573     public void processCancelImmediately(Vibration.EndInfo cancelInfo) {
574         if (Build.IS_DEBUGGABLE) {
575             expectIsVibrationThread(true);
576         }
577 
578         mCancelledImmediately = true;
579         mCancelledVibrationEndInfo = cancelInfo;
580         Step step;
581         while ((step = pollNext()) != null) {
582             step.cancelImmediately();
583         }
584         mPendingVibrateSteps = 0;
585     }
586 
587     /**
588      * Processes the vibrators that have sent their complete callbacks. A step is found that will
589      * accept the completion callback, and this step is brought forward for execution in the next
590      * run.
591      *
592      * <p>This assumes only one of the next steps is waiting on this given vibrator, so the
593      * first step found will be resumed by this method, in no particular order.
594      */
processVibratorsComplete(@onNull int[] vibratorsToProcess)595     private void processVibratorsComplete(@NonNull int[] vibratorsToProcess) {
596         if (Build.IS_DEBUGGABLE) {
597             expectIsVibrationThread(true);
598         }
599 
600         for (int vibratorId : vibratorsToProcess) {
601             Iterator<Step> it = mNextSteps.iterator();
602             while (it.hasNext()) {
603                 Step step = it.next();
604                 if (step.acceptVibratorCompleteCallback(vibratorId)) {
605                     it.remove();
606                     mPendingOnVibratorCompleteSteps.offer(step);
607                     break;
608                 }
609             }
610         }
611     }
612 
toSequential(CombinedVibration effect)613     private static CombinedVibration.Sequential toSequential(CombinedVibration effect) {
614         if (effect instanceof CombinedVibration.Sequential) {
615             return (CombinedVibration.Sequential) effect;
616         }
617         return (CombinedVibration.Sequential) CombinedVibration.startSequential()
618                 .addNext(effect)
619                 .combine();
620     }
621 
622     /**
623      * This check is used for debugging and documentation to indicate the thread that's expected
624      * to invoke a given public method on this class. Most methods are only invoked by
625      * VibrationThread, which is where all the steps and HAL calls should be made. Other threads
626      * should only signal to the execution flow being run by VibrationThread.
627      */
expectIsVibrationThread(boolean isVibrationThread)628     private static void expectIsVibrationThread(boolean isVibrationThread) {
629         if ((Thread.currentThread() instanceof VibrationThread) != isVibrationThread) {
630             Slog.wtfStack("VibrationStepConductor",
631                     "Thread caller assertion failed, expected isVibrationThread="
632                             + isVibrationThread);
633         }
634     }
635 }
636