Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 0616082f authored by TreeHugger Robot's avatar TreeHugger Robot Committed by Android (Google) Code Review
Browse files

Merge "Move cancel signal processing into VibrationStepConductor." into tm-dev

parents 8ec569f6 6d983f10
Loading
Loading
Loading
Loading
+149 −72
Original line number Diff line number Diff line
@@ -16,6 +16,7 @@

package com.android.server.vibrator;

import android.annotation.NonNull;
import android.annotation.Nullable;
import android.os.Build;
import android.os.CombinedVibration;
@@ -24,6 +25,7 @@ import android.os.vibrator.PrebakedSegment;
import android.os.vibrator.PrimitiveSegment;
import android.os.vibrator.RampSegment;
import android.os.vibrator.VibrationEffectSegment;
import android.util.IntArray;
import android.util.Slog;
import android.util.SparseArray;

@@ -69,9 +71,17 @@ final class VibrationStepConductor {

    private final PriorityQueue<Step> mNextSteps = new PriorityQueue<>();
    private final Queue<Step> mPendingOnVibratorCompleteSteps = new LinkedList<>();

    // Signalling fields.
    @GuardedBy("mLock")
    private final IntArray mSignalVibratorsComplete;
    @GuardedBy("mLock")
    private boolean mSignalCancel = false;
    @GuardedBy("mLock")
    private Queue<Integer> mCompletionNotifiedVibrators = new LinkedList<>();
    private boolean mSignalCancelImmediate = false;

    private boolean mCancelled = false;
    private boolean mCancelledImmediately = false;  // hard stop
    private int mPendingVibrateSteps;
    private int mRemainingStartSequentialEffectSteps;
    private int mSuccessfulVibratorOnSteps;
@@ -91,6 +101,7 @@ final class VibrationStepConductor {
                mVibrators.put(availableVibrators.keyAt(i), availableVibrators.valueAt(i));
            }
        }
        this.mSignalVibratorsComplete = new IntArray(mVibrators.size());
    }

    @Nullable
@@ -152,6 +163,10 @@ final class VibrationStepConductor {
        if (Build.IS_DEBUGGABLE) {
            expectIsVibrationThread(true);
        }
        if (mCancelledImmediately) {
            return true;  // Terminate.
        }

        // No need to check for vibration complete callbacks - if there were any, they would
        // have no steps to notify anyway.
        return mPendingOnVibratorCompleteSteps.isEmpty() && mNextSteps.isEmpty();
@@ -166,6 +181,9 @@ final class VibrationStepConductor {
            expectIsVibrationThread(true);
        }

        if (mCancelled) {
            return Vibration.Status.CANCELLED;
        }
        if (mPendingVibrateSteps > 0
                || mRemainingStartSequentialEffectSteps > 0) {
            return Vibration.Status.RUNNING;
@@ -180,48 +198,65 @@ final class VibrationStepConductor {

    /**
     * Blocks until the next step is due to run. The wait here may be interrupted by calling
     * {@link #notifyWakeUp} or other "notify" methods.
     * one of the "notify" methods.
     *
     * <p>This method returns false if the next step is ready to run now. If the method returns
     * true, then some waiting was done, but may have been interrupted by a wakeUp.
     * <p>This method returns true if the next step is ready to run now. If the method returns
     * false, then some waiting was done, but may have been interrupted by a wakeUp, and the
     * status and isFinished of the vibration should be re-checked before calling this method again.
     *
     * @return true if the method waited at all, or false if a step is ready to run now.
     * @return true if the next step can be run now or the vibration is finished, or false if this
     *   method waited and the conductor state may have changed asynchronously, in which case this
     *   method needs to be run again.
     */
    public boolean waitUntilNextStepIsDue() {
        if (Build.IS_DEBUGGABLE) {
            expectIsVibrationThread(true);
        }
        // It's necessary to re-process callbacks if they come in after acquiring the lock to
        // start waiting, but we don't want to hold the lock while processing them.
        // The loop goes until there are no pending callbacks to process.
        while (true) {
            // TODO: cancellation checking could also be integrated here, instead of outside in
            // VibrationThread.
            processVibratorCompleteCallbacks();
            if (!mPendingOnVibratorCompleteSteps.isEmpty()) {
                // Steps resumed by vibrator complete callback should be played right away.

        processAllNotifySignals();
        if (mCancelledImmediately) {
            // Don't try to run a step for immediate cancel, although there should be none left.
            // Non-immediate cancellation may have cleanup steps, so it continues processing.
            return false;
        }
        if (!mPendingOnVibratorCompleteSteps.isEmpty()) {
            return true;  // Resumed step ready.
        }
        Step nextStep = mNextSteps.peek();
        if (nextStep == null) {
                return false;
            return true;  // Finished
        }
        long waitMillis = nextStep.calculateWaitTime();
        if (waitMillis <= 0) {
                return false;
            return true;  // Regular step ready
        }
        synchronized (mLock) {
                // Double check for missed wake-ups before sleeping.
                if (!mCompletionNotifiedVibrators.isEmpty()) {
                    continue;  // Start again: processVibratorCompleteCallbacks will consume it.
            // Double check for signals before sleeping, as their notify wouldn't interrupt a fresh
            // wait.
            if (hasPendingNotifySignalLocked()) {
                // Don't run the next step, it will loop back to this method and process them.
                return false;
            }
            try {
                mLock.wait(waitMillis);
            } catch (InterruptedException e) {
            }
                return true;
            return false;  // Caller needs to check isFinished and maybe wait again.
        }
    }

    @Nullable
    private Step pollNext() {
        if (Build.IS_DEBUGGABLE) {
            expectIsVibrationThread(true);
        }

        // Prioritize the steps resumed by a vibrator complete callback, irrespective of their
        // "next run time".
        if (!mPendingOnVibratorCompleteSteps.isEmpty()) {
            return mPendingOnVibratorCompleteSteps.poll();
        }
        return mNextSteps.poll();
    }

    /**
@@ -255,20 +290,27 @@ final class VibrationStepConductor {
    }

    /**
     * Wake up the execution thread, which may be waiting until the next step is due.
     * The caller is responsible for diverting VibrationThread execution.
     * Notify the execution that cancellation is requested. This will be acted upon
     * asynchronously in the VibrationThread.
     *
     * <p>At the moment this is used after the signal is set that a cancellation needs to be
     * processed. The actual cancellation will be invoked from the VibrationThread.
     * @param immediate indicates whether cancellation should abort urgently and skip cleanup steps.
     */
    public void notifyWakeUp() {
    public void notifyCancelled(boolean immediate) {
        if (Build.IS_DEBUGGABLE) {
            expectIsVibrationThread(false);
        }

        synchronized (mLock) {
            if (immediate && mSignalCancelImmediate || mSignalCancel) {
                // Nothing to update: already cancelled previously.
                return;
            }
            mSignalCancelImmediate |= immediate;
            mSignalCancel = true;
            mLock.notify();
        }
        if (DEBUG) {
            Slog.d(TAG, "Vibration cancel requested, immediate=" + immediate);
        }
    }

    /**
@@ -287,7 +329,7 @@ final class VibrationStepConductor {
        }

        synchronized (mLock) {
            mCompletionNotifiedVibrators.offer(vibratorId);
            mSignalVibratorsComplete.add(vibratorId);
            mLock.notify();
        }
    }
@@ -310,23 +352,80 @@ final class VibrationStepConductor {

        synchronized (mLock) {
            for (int i = 0; i < mVibrators.size(); i++) {
                mCompletionNotifiedVibrators.offer(mVibrators.keyAt(i));
                mSignalVibratorsComplete.add(mVibrators.keyAt(i));
            }
            mLock.notify();
        }
    }

    @GuardedBy("mLock")
    private boolean hasPendingNotifySignalLocked() {
        if (Build.IS_DEBUGGABLE) {
            expectIsVibrationThread(true);  // Reads VibrationThread variables as well as signals.
        }
        return (mSignalCancel && !mCancelled)
            || (mSignalCancelImmediate && !mCancelledImmediately)
            || (mSignalVibratorsComplete.size() > 0);
    }

    /**
     * Process any notified cross-thread signals, applying the necessary VibrationThread state
     * changes.
     */
    private void processAllNotifySignals() {
        if (Build.IS_DEBUGGABLE) {
            expectIsVibrationThread(true);
        }

        int[] vibratorsToProcess = null;
        boolean doCancel = false;
        boolean doCancelImmediate = false;
        // Swap out the queue of completions to process.
        synchronized (mLock) {
            if (mSignalCancelImmediate) {
                if (mCancelledImmediately) {
                    Slog.wtf(TAG, "Immediate cancellation signal processed twice");
                }
                // This should only happen once.
                doCancelImmediate = true;
            }
            if (mSignalCancel && !mCancelled) {
                doCancel = true;
            }
            if (!doCancelImmediate && mSignalVibratorsComplete.size() > 0) {
                vibratorsToProcess = mSignalVibratorsComplete.toArray();  // makes a copy
                mSignalVibratorsComplete.clear();
            }
        }

        // Force cancellation means stop everything and clear all steps, so the execution loop
        // shouldn't come back to this method. To observe explicitly: this drops vibrator
        // completion signals that were collected in this call, but we won't process them
        // anyway as all steps are cancelled.
        if (doCancelImmediate) {
            processCancelImmediately();
            return;
        }
        if (doCancel) {
            processCancel();
        }
        if (vibratorsToProcess != null) {
            processVibratorsComplete(vibratorsToProcess);
        }
    }

    /**
     * Cancel the current queue, replacing all remaining steps with respective clean-up steps.
     *
     * <p>This will remove all steps and replace them with respective
     * <p>This will remove all steps and replace them with respective results of
     * {@link Step#cancel()}.
     */
    public void cancel() {
    public void processCancel() {
        if (Build.IS_DEBUGGABLE) {
            expectIsVibrationThread(true);
        }

        mCancelled = true;
        // Vibrator callbacks should wait until all steps from the queue are properly cancelled
        // and clean up steps are added back to the queue, so they can handle the callback.
        List<Step> cleanUpSteps = new ArrayList<>();
@@ -344,11 +443,13 @@ final class VibrationStepConductor {
     *
     * <p>This will remove and trigger {@link Step#cancelImmediately()} in all steps, in order.
     */
    public void cancelImmediately() {
    public void processCancelImmediately() {
        if (Build.IS_DEBUGGABLE) {
            expectIsVibrationThread(true);
        }

        mCancelledImmediately = true;
        mCancelled = true;
        Step step;
        while ((step = pollNext()) != null) {
            step.cancelImmediately();
@@ -356,44 +457,20 @@ final class VibrationStepConductor {
        mPendingVibrateSteps = 0;
    }

    @Nullable
    private Step pollNext() {
        if (Build.IS_DEBUGGABLE) {
            expectIsVibrationThread(true);
        }

        // Prioritize the steps resumed by a vibrator complete callback, irrespective of their
        // "next run time".
        if (!mPendingOnVibratorCompleteSteps.isEmpty()) {
            return mPendingOnVibratorCompleteSteps.poll();
        }
        return mNextSteps.poll();
    }

    /**
     * Process any notified vibrator completions.
     * Processes the vibrators that have sent their complete callbacks. A step is found that will
     * accept the completion callback, and this step is brought forward for execution in the next
     * run.
     *
     * <p>This assumes only one of the next steps is waiting on this given vibrator, so the
     * first step found will be resumed by this method, in no particular order.
     */
    private void processVibratorCompleteCallbacks() {
    private void processVibratorsComplete(@NonNull int[] vibratorsToProcess) {
        if (Build.IS_DEBUGGABLE) {
            expectIsVibrationThread(true);
        }

        Queue<Integer> vibratorsToProcess;
        // Swap out the queue of completions to process.
        synchronized (mLock) {
            if (mCompletionNotifiedVibrators.isEmpty()) {
                return;  // Nothing to do.
            }

            vibratorsToProcess = mCompletionNotifiedVibrators;
            mCompletionNotifiedVibrators = new LinkedList<>();
        }

        while (!vibratorsToProcess.isEmpty()) {
            int vibratorId = vibratorsToProcess.poll();
        for (int vibratorId : vibratorsToProcess) {
            Iterator<Step> it = mNextSteps.iterator();
            while (it.hasNext()) {
                Step step = it.next();
+7 −37
Original line number Diff line number Diff line
@@ -108,7 +108,7 @@ final class VibrationThread extends Thread implements IBinder.DeathRecipient {
        if (DEBUG) {
            Slog.d(TAG, "Binder died, cancelling vibration...");
        }
        cancel();
        mStepConductor.notifyCancelled(/* immediate= */ false);
    }

    @Override
@@ -168,28 +168,12 @@ final class VibrationThread extends Thread implements IBinder.DeathRecipient {

    /** Cancel current vibration and ramp down the vibrators gracefully. */
    public void cancel() {
        if (mStop) {
            // Already cancelled, running clean-up steps.
            return;
        }
        mStop = true;
        if (DEBUG) {
            Slog.d(TAG, "Vibration cancelled");
        }
        mStepConductor.notifyWakeUp();
        mStepConductor.notifyCancelled(/* immediate= */ false);
    }

    /** Cancel current vibration and shuts off the vibrators immediately. */
    public void cancelImmediately() {
        if (mForceStop) {
            // Already forced the thread to stop, wait for it to finish.
            return;
        }
        if (DEBUG) {
            Slog.d(TAG, "Vibration cancelled immediately");
        }
        mStop = mForceStop = true;
        mStepConductor.notifyWakeUp();
        mStepConductor.notifyCancelled(/* immediate= */ true);
    }

    /** Notify current vibration that a synced step has completed. */
@@ -217,13 +201,10 @@ final class VibrationThread extends Thread implements IBinder.DeathRecipient {
        Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "playVibration");
        try {
            mStepConductor.prepareToStart();

            while (!mStepConductor.isFinished()) {
                // Skip wait and next step if mForceStop already happened.
                boolean waited = mForceStop || mStepConductor.waitUntilNextStepIsDue();
                // If we waited, don't run the next step, but instead re-evaluate cancellation
                // status
                if (!waited) {
                boolean readyToRun = mStepConductor.waitUntilNextStepIsDue();
                // If we waited, don't run the next step, but instead re-evaluate status.
                if (readyToRun) {
                    if (DEBUG) {
                        Slog.d(TAG, "Play vibration consuming next step...");
                    }
@@ -232,23 +213,12 @@ final class VibrationThread extends Thread implements IBinder.DeathRecipient {
                    mStepConductor.runNextStep();
                }

                if (mForceStop) {
                    // Cancel every step and stop playing them right away, even clean-up steps.
                    mStepConductor.cancelImmediately();
                    clientVibrationCompleteIfNotAlready(Vibration.Status.CANCELLED);
                    break;
                }

                Vibration.Status status = mStop ? Vibration.Status.CANCELLED
                        : mStepConductor.calculateVibrationStatus();
                Vibration.Status status = mStepConductor.calculateVibrationStatus();
                // This block can only run once due to mCalledVibrationCompleteCallback.
                if (status != Vibration.Status.RUNNING && !mCalledVibrationCompleteCallback) {
                    // First time vibration stopped running, start clean-up tasks and notify
                    // callback immediately.
                    clientVibrationCompleteIfNotAlready(status);
                    if (status == Vibration.Status.CANCELLED) {
                        mStepConductor.cancel();
                    }
                }
            }
        } finally {