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

Commit 902d458a authored by TreeHugger Robot's avatar TreeHugger Robot Committed by Automerger Merge Worker
Browse files

Merge "Move cancel signal processing into VibrationStepConductor." into tm-dev am: 0616082f

Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/16988994

Change-Id: I781ad488cccfdeca49109c1844db2249d1de8d7e
parents f15578c9 0616082f
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 {