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