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

Commit b189754f authored by Simon Bowden's avatar Simon Bowden Committed by Automerger Merge Worker
Browse files

Merge "Make VibrationThread long-lived." into tm-dev am: 159518e2

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

Change-Id: Ibeb8cc4caeb137d334ba6ab812f2954ea669576d
parents 5ba0d4a0 159518e2
Loading
Loading
Loading
Loading
+2 −0
Original line number Diff line number Diff line
@@ -49,6 +49,8 @@ final class Vibration {
        FORWARDED_TO_INPUT_DEVICES,
        CANCELLED,
        IGNORED_ERROR_APP_OPS,
        IGNORED_ERROR_CANCELLING,
        IGNORED_ERROR_SCHEDULING,
        IGNORED_ERROR_TOKEN,
        IGNORED,
        IGNORED_APP_OPS,
+160 −59
Original line number Diff line number Diff line
@@ -16,6 +16,8 @@

package com.android.server.vibrator;

import android.annotation.NonNull;
import android.annotation.Nullable;
import android.os.IBinder;
import android.os.PowerManager;
import android.os.Process;
@@ -23,9 +25,12 @@ import android.os.RemoteException;
import android.os.Trace;
import android.os.WorkSource;
import android.util.Slog;
import android.util.SparseArray;

import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;

import java.util.NoSuchElementException;
import java.util.Objects;

/** Plays a {@link Vibration} in dedicated thread. */
final class VibrationThread extends Thread implements IBinder.DeathRecipient {
@@ -76,31 +81,41 @@ final class VibrationThread extends Thread implements IBinder.DeathRecipient {
         * Tells the manager that the VibrationThread is finished with the previous vibration and
         * all of its cleanup tasks, and the vibrators can now be used for another vibration.
         */
        void onVibrationThreadReleased();
        void onVibrationThreadReleased(long vibrationId);
    }

    private final PowerManager.WakeLock mWakeLock;
    private final VibrationThread.VibratorManagerHooks mVibratorManagerHooks;

    private final VibrationStepConductor mStepConductor;
    // mLock is used here to communicate that the thread's work status has changed. The
    // VibrationThread is expected to wait until work arrives, and other threads may wait until
    // work has finished. Therefore, any changes to the conductor must be followed by a notifyAll
    // so that threads check if their desired state is achieved.
    private final Object mLock = new Object();

    /**
     * The conductor that is intended to be active. Null value means that a new conductor can
     * be set to run. Note that this field is only reset to null when mExecutingConductor has
     * completed, so the two fields should be in sync.
     */
    @GuardedBy("mLock")
    @Nullable
    private VibrationStepConductor mRequestedActiveConductor;

    /**
     * The conductor being executed by this thread, should only be accessed within this thread's
     * execution. i.e. not thread-safe. {@link #mRequestedActiveConductor} is for cross-thread
     * signalling.
     */
    @Nullable
    private VibrationStepConductor mExecutingConductor;

    private volatile boolean mStop;
    private volatile boolean mForceStop;
    // Variable only set and read in main thread.
    // Variable only set and read in main thread, no need to lock.
    private boolean mCalledVibrationCompleteCallback = false;

    VibrationThread(Vibration vib, VibrationSettings vibrationSettings,
            DeviceVibrationEffectAdapter effectAdapter,
            SparseArray<VibratorController> availableVibrators, PowerManager.WakeLock wakeLock,
            VibratorManagerHooks vibratorManagerHooks) {
        mVibratorManagerHooks = vibratorManagerHooks;
    VibrationThread(PowerManager.WakeLock wakeLock, VibratorManagerHooks vibratorManagerHooks) {
        mWakeLock = wakeLock;
        mStepConductor = new VibrationStepConductor(vib, vibrationSettings, effectAdapter,
                availableVibrators, vibratorManagerHooks);
    }

    Vibration getVibration() {
        return mStepConductor.getVibration();
        mVibratorManagerHooks = vibratorManagerHooks;
    }

    @Override
@@ -108,32 +123,138 @@ final class VibrationThread extends Thread implements IBinder.DeathRecipient {
        if (DEBUG) {
            Slog.d(TAG, "Binder died, cancelling vibration...");
        }
        mStepConductor.notifyCancelled(/* immediate= */ false);
        // The binder death link only exists while the conductor is set.
        // TODO: move the death linking to be associated with the conductor directly, this
        // awkwardness will go away.
        VibrationStepConductor conductor;
        synchronized (mLock) {
            conductor = mRequestedActiveConductor;
        }
        if (conductor != null) {
            conductor.notifyCancelled(/* immediate= */ false);
        }
    }

    /**
     * Sets/activates the current vibration. Must only be called after receiving
     * onVibratorsReleased from the previous vibration.
     *
     * @return false if VibrationThread couldn't accept it, which shouldn't happen unless called
     *  before the release callback.
     */
    boolean runVibrationOnVibrationThread(VibrationStepConductor conductor) {
        synchronized (mLock) {
            if (mRequestedActiveConductor != null) {
                Slog.wtf(TAG, "Attempt to start vibration when one already running");
                return false;
            }
            mRequestedActiveConductor = conductor;
            mLock.notifyAll();
        }
        return true;
    }

    @Override
    public void run() {
        // Structured to guarantee the vibrators completed and released callbacks at the end of
        // thread execution. Both of these callbacks are exclusively called from this thread.
        Process.setThreadPriority(Process.THREAD_PRIORITY_URGENT_DISPLAY);
        while (true) {
            // mExecutingConductor is only modified in this loop.
            mExecutingConductor = Objects.requireNonNull(waitForVibrationRequest());

            mCalledVibrationCompleteCallback = false;
            runCurrentVibrationWithWakeLock();
            if (!mExecutingConductor.isFinished()) {
                Slog.wtf(TAG, "VibrationThread terminated with unfinished vibration");
            }
            synchronized (mLock) {
                // Allow another vibration to be requested.
                mRequestedActiveConductor = null;
            }
            // The callback is run without holding the lock, as it may initiate another vibration.
            // It's safe to notify even if mVibratorConductor has been re-written, as the "wait"
            // methods all verify their waited state before returning. In reality though, if the
            // manager is waiting for the thread to finish, then there is no pending vibration
            // for this thread.
            // No point doing this in finally, as if there's an exception, this thread will die
            // and be unusable anyway.
            mVibratorManagerHooks.onVibrationThreadReleased(mExecutingConductor.getVibration().id);
            synchronized (mLock) {
                mLock.notifyAll();
            }
            mExecutingConductor = null;
        }
    }

    /**
     * Waits until the VibrationThread has finished processing, timing out after the given
     * number of milliseconds. In general, external locking will manage the ordering of this
     * with calls to {@link #runVibrationOnVibrationThread}.
     *
     * @return true if the vibration completed, or false if waiting timed out.
     */
    public boolean waitForThreadIdle(long maxWaitMillis) {
        long now = System.currentTimeMillis();
        long deadline = now + maxWaitMillis;
        synchronized (mLock) {
            while (true) {
                if (mRequestedActiveConductor == null) {
                    return true;  // Done
                }
                if (now >= deadline) {  // Note that thread.wait(0) waits indefinitely.
                    return false;  // Timed out.
                }
                try {
                    mLock.wait(deadline - now);
                } catch (InterruptedException e) {
                    Slog.w(TAG, "VibrationThread interrupted waiting to stop, continuing");
                }
                now = System.currentTimeMillis();
            }
        }
    }

    /** Waits for a signal indicating a vibration is ready to run, then returns its conductor. */
    @NonNull
    private VibrationStepConductor waitForVibrationRequest() {
        while (true) {
            synchronized (mLock) {
                if (mRequestedActiveConductor != null) {
                    return mRequestedActiveConductor;
                }
                try {
                Process.setThreadPriority(Process.THREAD_PRIORITY_URGENT_DISPLAY);
                runWithWakeLock();
            } finally {
                clientVibrationCompleteIfNotAlready(Vibration.Status.FINISHED_UNEXPECTED);
                    mLock.wait();
                } catch (InterruptedException e) {
                    Slog.w(TAG, "VibrationThread interrupted waiting to start, continuing");
                }
            }
        } finally {
            mVibratorManagerHooks.onVibrationThreadReleased();
        }
    }

    /**
     * Only for testing: this method relies on the requested-active conductor, rather than
     * the executing conductor that's not intended for other threads.
     *
     * @return true if the vibration that's currently desired to be active has the given id.
     */
    @VisibleForTesting
    boolean isRunningVibrationId(long id) {
        synchronized (mLock) {
            return (mRequestedActiveConductor != null
                    && mRequestedActiveConductor.getVibration().id == id);
        }
    }

    /** Runs the VibrationThread ensuring that the wake lock is acquired and released. */
    private void runWithWakeLock() {
        WorkSource workSource = new WorkSource(mStepConductor.getVibration().uid);
    private void runCurrentVibrationWithWakeLock() {
        WorkSource workSource = new WorkSource(mExecutingConductor.getVibration().uid);
        mWakeLock.setWorkSource(workSource);
        mWakeLock.acquire();
        try {
            runWithWakeLockAndDeathLink();
            try {
                runCurrentVibrationWithWakeLockAndDeathLink();
            } finally {
                clientVibrationCompleteIfNotAlready(Vibration.Status.FINISHED_UNEXPECTED);
            }
        } finally {
            mWakeLock.release();
            mWakeLock.setWorkSource(null);
@@ -144,8 +265,8 @@ final class VibrationThread extends Thread implements IBinder.DeathRecipient {
     * Runs the VibrationThread with the binder death link, handling link/unlink failures.
     * Called from within runWithWakeLock.
     */
    private void runWithWakeLockAndDeathLink() {
        IBinder vibrationBinderToken = mStepConductor.getVibration().token;
    private void runCurrentVibrationWithWakeLockAndDeathLink() {
        IBinder vibrationBinderToken = mExecutingConductor.getVibration().token;
        try {
            vibrationBinderToken.linkToDeath(this, 0);
        } catch (RemoteException e) {
@@ -166,26 +287,6 @@ final class VibrationThread extends Thread implements IBinder.DeathRecipient {
        }
    }

    /** Cancel current vibration and ramp down the vibrators gracefully. */
    public void cancel() {
        mStepConductor.notifyCancelled(/* immediate= */ false);
    }

    /** Cancel current vibration and shuts off the vibrators immediately. */
    public void cancelImmediately() {
        mStepConductor.notifyCancelled(/* immediate= */ true);
    }

    /** Notify current vibration that a synced step has completed. */
    public void syncedVibrationComplete() {
        mStepConductor.notifySyncedVibrationComplete();
    }

    /** Notify current vibration that a step has completed on given vibrator. */
    public void vibratorComplete(int vibratorId) {
        mStepConductor.notifyVibratorComplete(vibratorId);
    }

    // Indicate that the vibration is complete. This can be called multiple times only for
    // convenience of handling error conditions - an error after the client is complete won't
    // affect the status.
@@ -193,16 +294,16 @@ final class VibrationThread extends Thread implements IBinder.DeathRecipient {
        if (!mCalledVibrationCompleteCallback) {
            mCalledVibrationCompleteCallback = true;
            mVibratorManagerHooks.onVibrationCompleted(
                    mStepConductor.getVibration().id, completedStatus);
                    mExecutingConductor.getVibration().id, completedStatus);
        }
    }

    private void playVibration() {
        Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "playVibration");
        try {
            mStepConductor.prepareToStart();
            while (!mStepConductor.isFinished()) {
                boolean readyToRun = mStepConductor.waitUntilNextStepIsDue();
            mExecutingConductor.prepareToStart();
            while (!mExecutingConductor.isFinished()) {
                boolean readyToRun = mExecutingConductor.waitUntilNextStepIsDue();
                // If we waited, don't run the next step, but instead re-evaluate status.
                if (readyToRun) {
                    if (DEBUG) {
@@ -210,10 +311,10 @@ final class VibrationThread extends Thread implements IBinder.DeathRecipient {
                    }
                    // Run the step without holding the main lock, to avoid HAL interactions from
                    // blocking the thread.
                    mStepConductor.runNextStep();
                    mExecutingConductor.runNextStep();
                }

                Vibration.Status status = mStepConductor.calculateVibrationStatus();
                Vibration.Status status = mExecutingConductor.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
+56 −36
Original line number Diff line number Diff line
@@ -31,6 +31,7 @@ import android.content.pm.PackageManager;
import android.hardware.vibrator.IVibrator;
import android.os.BatteryStats;
import android.os.Binder;
import android.os.Build;
import android.os.CombinedVibration;
import android.os.ExternalVibration;
import android.os.Handler;
@@ -52,6 +53,7 @@ import android.os.VibrationEffect;
import android.os.VibratorInfo;
import android.os.vibrator.PrebakedSegment;
import android.os.vibrator.VibrationEffectSegment;
import android.text.TextUtils;
import android.util.Slog;
import android.util.SparseArray;
import android.util.proto.ProtoOutputStream;
@@ -91,6 +93,12 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
    /** Fixed large duration used to note repeating vibrations to {@link IBatteryStats}. */
    private static final long BATTERY_STATS_REPEATING_VIBRATION_DURATION = 5_000;

    /**
     * Maximum millis to wait for a vibration thread cancellation to "clean up" and finish, when
     * blocking for an external vibration. In practice, this should be plenty.
     */
    private static final long VIBRATION_CANCEL_WAIT_MILLIS = 5000;

    /** Lifecycle responsible for initializing this class at the right system server phases. */
    public static class Lifecycle extends SystemService {
        private VibratorManagerService mService;
@@ -121,6 +129,7 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
    private final PowerManager.WakeLock mWakeLock;
    private final IBatteryStats mBatteryStatsService;
    private final Handler mHandler;
    private final VibrationThread mVibrationThread;
    private final AppOpsManager mAppOps;
    private final NativeWrapper mNativeWrapper;
    private final VibratorManagerRecords mVibratorManagerRecords;
@@ -132,9 +141,9 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
    @GuardedBy("mLock")
    private final SparseArray<AlwaysOnVibration> mAlwaysOnEffects = new SparseArray<>();
    @GuardedBy("mLock")
    private VibrationThread mCurrentVibration;
    private VibrationStepConductor mCurrentVibration;
    @GuardedBy("mLock")
    private VibrationThread mNextVibration;
    private VibrationStepConductor mNextVibration;
    @GuardedBy("mLock")
    private ExternalVibrationHolder mCurrentExternalVibration;
    @GuardedBy("mLock")
@@ -156,7 +165,7 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
                        clearNextVibrationLocked(Vibration.Status.CANCELLED);
                    }
                    if (shouldCancelOnScreenOffLocked(mCurrentVibration)) {
                        mCurrentVibration.cancel();
                        mCurrentVibration.notifyCancelled(/* immediate= */ false);
                    }
                }
            }
@@ -202,6 +211,8 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
        PowerManager pm = context.getSystemService(PowerManager.class);
        mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "*vibrator*");
        mWakeLock.setReferenceCounted(true);
        mVibrationThread = new VibrationThread(mWakeLock, mVibrationThreadCallbacks);
        mVibrationThread.start();

        // Load vibrator hardware info. The vibrator ids and manager capabilities are loaded only
        // once and assumed unchanged for the lifecycle of this service. Each individual vibrator
@@ -409,7 +420,7 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
                final long ident = Binder.clearCallingIdentity();
                try {
                    if (mCurrentVibration != null) {
                        mCurrentVibration.cancel();
                        mCurrentVibration.notifyCancelled(/* immediate= */ false);
                    }
                    Vibration.Status status = startVibrationLocked(vib);
                    if (status != Vibration.Status.RUNNING) {
@@ -447,7 +458,7 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
                    if (mCurrentVibration != null
                            && shouldCancelVibration(mCurrentVibration.getVibration(),
                            usageFilter, token)) {
                        mCurrentVibration.cancel();
                        mCurrentVibration.notifyCancelled(/* immediate= */false);
                    }
                    if (mCurrentExternalVibration != null
                            && shouldCancelVibration(
@@ -584,7 +595,7 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
                    Slog.d(TAG, "Canceling vibration because settings changed: "
                            + (inputDevicesChanged ? "input devices changed" : ignoreStatus));
                }
                mCurrentVibration.cancel();
                mCurrentVibration.notifyCancelled(/* immediate= */ false);
            }
        }
    }
@@ -626,17 +637,15 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
                return Vibration.Status.FORWARDED_TO_INPUT_DEVICES;
            }

            VibrationThread vibThread = new VibrationThread(vib, mVibrationSettings,
                    mDeviceVibrationEffectAdapter, mVibrators, mWakeLock,
                    mVibrationThreadCallbacks);

            VibrationStepConductor conductor = new VibrationStepConductor(vib, mVibrationSettings,
                    mDeviceVibrationEffectAdapter, mVibrators, mVibrationThreadCallbacks);
            if (mCurrentVibration == null) {
                return startVibrationThreadLocked(vibThread);
                return startVibrationOnThreadLocked(conductor);
            }
            // If there's already a vibration queued (waiting for the previous one to finish
            // cancelling), end it cleanly and replace it with the new one.
            clearNextVibrationLocked(Vibration.Status.IGNORED_SUPERSEDED);
            mNextVibration = vibThread;
            mNextVibration = conductor;
            return Vibration.Status.RUNNING;
        } finally {
            Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
@@ -644,16 +653,20 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
    }

    @GuardedBy("mLock")
    private Vibration.Status startVibrationThreadLocked(VibrationThread vibThread) {
    private Vibration.Status startVibrationOnThreadLocked(VibrationStepConductor conductor) {
        Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "startVibrationThreadLocked");
        try {
            Vibration vib = vibThread.getVibration();
            Vibration vib = conductor.getVibration();
            int mode = startAppOpModeLocked(vib.uid, vib.opPkg, vib.attrs);
            switch (mode) {
                case AppOpsManager.MODE_ALLOWED:
                    Trace.asyncTraceBegin(Trace.TRACE_TAG_VIBRATOR, "vibration", 0);
                    mCurrentVibration = vibThread;
                    mCurrentVibration.start();
                    mCurrentVibration = conductor;
                    if (!mVibrationThread.runVibrationOnVibrationThread(mCurrentVibration)) {
                        // Shouldn't happen. The method call already logs a wtf.
                        mCurrentVibration = null;  // Aborted.
                        return Vibration.Status.IGNORED_ERROR_SCHEDULING;
                    }
                    return Vibration.Status.RUNNING;
                case AppOpsManager.MODE_ERRORED:
                    Slog.w(TAG, "Start AppOpsManager operation errored for uid " + vib.uid);
@@ -741,7 +754,7 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
                if (DEBUG) {
                    Slog.d(TAG, "Synced vibration " + vibrationId + " complete, notifying thread");
                }
                mCurrentVibration.syncedVibrationComplete();
                mCurrentVibration.notifySyncedVibrationComplete();
            }
        }
    }
@@ -753,7 +766,7 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
                    Slog.d(TAG, "Vibration " + vibrationId + " on vibrator " + vibratorId
                            + " complete, notifying thread");
                }
                mCurrentVibration.vibratorComplete(vibratorId);
                mCurrentVibration.notifyVibratorComplete(vibratorId);
            }
        }
    }
@@ -1064,11 +1077,11 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
    }

    @GuardedBy("mLock")
    private boolean shouldCancelOnScreenOffLocked(@Nullable VibrationThread vibrationThread) {
        if (vibrationThread == null) {
    private boolean shouldCancelOnScreenOffLocked(@Nullable VibrationStepConductor conductor) {
        if (conductor == null) {
            return false;
        }
        Vibration vib = vibrationThread.getVibration();
        Vibration vib = conductor.getVibration();
        return mVibrationSettings.shouldCancelVibrationOnScreenOff(
                vib.uid, vib.opPkg, vib.attrs.getUsage());
    }
@@ -1185,21 +1198,27 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
        }

        @Override
        public void onVibrationThreadReleased() {
        public void onVibrationThreadReleased(long vibrationId) {
            if (DEBUG) {
                Slog.d(TAG, "Vibrators released after finished vibration");
                Slog.d(TAG, "VibrationThread released after finished vibration");
            }
            synchronized (mLock) {
                if (DEBUG) {
                    Slog.d(TAG, "Processing vibrators released callback");
                    Slog.d(TAG, "Processing VibrationThread released callback");
                }
                if (Build.IS_DEBUGGABLE && mCurrentVibration != null
                        && mCurrentVibration.getVibration().id != vibrationId) {
                    Slog.wtf(TAG, TextUtils.formatSimple(
                            "VibrationId mismatch on release. expected=%d, released=%d",
                            mCurrentVibration.getVibration().id, vibrationId));
                }
                mCurrentVibration = null;
                if (mNextVibration != null) {
                    VibrationThread vibThread = mNextVibration;
                    VibrationStepConductor nextConductor = mNextVibration;
                    mNextVibration = null;
                    Vibration.Status status = startVibrationThreadLocked(vibThread);
                    Vibration.Status status = startVibrationOnThreadLocked(nextConductor);
                    if (status != Vibration.Status.RUNNING) {
                        endVibrationLocked(vibThread.getVibration(), status);
                        endVibrationLocked(nextConductor.getVibration(), status);
                    }
                }
            }
@@ -1451,7 +1470,7 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
            }

            ExternalVibrationHolder cancelingExternalVibration = null;
            VibrationThread cancelingVibration = null;
            boolean waitForCompletion = false;
            int scale;
            synchronized (mLock) {
                Vibration.Status ignoreStatus = shouldIgnoreVibrationLocked(
@@ -1473,8 +1492,8 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
                    // vibration that may be playing and ready the vibrator for external control.
                    if (mCurrentVibration != null) {
                        clearNextVibrationLocked(Vibration.Status.IGNORED_FOR_EXTERNAL);
                        mCurrentVibration.cancelImmediately();
                        cancelingVibration = mCurrentVibration;
                        mCurrentVibration.notifyCancelled(/* immediate= */ true);
                        waitForCompletion = true;
                    }
                } else {
                    // At this point we have an externally controlled vibration playing already.
@@ -1497,12 +1516,13 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
                scale = mCurrentExternalVibration.scale;
            }

            if (cancelingVibration != null) {
                try {
                    cancelingVibration.join();
                } catch (InterruptedException e) {
                    Slog.w("Interrupted while waiting for vibration to finish before starting "
                            + "external control", e);
            if (waitForCompletion) {
                if (!mVibrationThread.waitForThreadIdle(VIBRATION_CANCEL_WAIT_MILLIS)) {
                    Slog.e(TAG, "Timed out waiting for vibration to cancel");
                    synchronized (mLock) {
                        stopExternalVibrateLocked(Vibration.Status.IGNORED_ERROR_CANCELLING);
                    }
                    return IExternalVibratorService.SCALE_MUTE;
                }
            }
            if (cancelingExternalVibration == null) {
+41 −11

File changed.

Preview size limit exceeded, changes collapsed.

+325 −215

File changed.

Preview size limit exceeded, changes collapsed.

Loading