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