Loading services/core/java/com/android/server/vibrator/Vibration.java +2 −0 Original line number Diff line number Diff line Loading @@ -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, Loading services/core/java/com/android/server/vibrator/VibrationThread.java +160 −59 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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 { Loading Loading @@ -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 Loading @@ -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); Loading @@ -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) { Loading @@ -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. Loading @@ -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) { Loading @@ -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 Loading services/core/java/com/android/server/vibrator/VibratorManagerService.java +56 −36 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading Loading @@ -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; Loading Loading @@ -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; Loading @@ -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") Loading @@ -156,7 +165,7 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { clearNextVibrationLocked(Vibration.Status.CANCELLED); } if (shouldCancelOnScreenOffLocked(mCurrentVibration)) { mCurrentVibration.cancel(); mCurrentVibration.notifyCancelled(/* immediate= */ false); } } } Loading Loading @@ -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 Loading Loading @@ -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) { Loading Loading @@ -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( Loading Loading @@ -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); } } } Loading Loading @@ -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); Loading @@ -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); Loading Loading @@ -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(); } } } Loading @@ -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); } } } Loading Loading @@ -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()); } Loading Loading @@ -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); } } } Loading Loading @@ -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( Loading @@ -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. Loading @@ -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) { Loading Loading
services/core/java/com/android/server/vibrator/Vibration.java +2 −0 Original line number Diff line number Diff line Loading @@ -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, Loading
services/core/java/com/android/server/vibrator/VibrationThread.java +160 −59 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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 { Loading Loading @@ -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 Loading @@ -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); Loading @@ -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) { Loading @@ -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. Loading @@ -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) { Loading @@ -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 Loading
services/core/java/com/android/server/vibrator/VibratorManagerService.java +56 −36 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading Loading @@ -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; Loading Loading @@ -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; Loading @@ -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") Loading @@ -156,7 +165,7 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { clearNextVibrationLocked(Vibration.Status.CANCELLED); } if (shouldCancelOnScreenOffLocked(mCurrentVibration)) { mCurrentVibration.cancel(); mCurrentVibration.notifyCancelled(/* immediate= */ false); } } } Loading Loading @@ -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 Loading Loading @@ -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) { Loading Loading @@ -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( Loading Loading @@ -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); } } } Loading Loading @@ -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); Loading @@ -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); Loading Loading @@ -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(); } } } Loading @@ -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); } } } Loading Loading @@ -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()); } Loading Loading @@ -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); } } } Loading Loading @@ -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( Loading @@ -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. Loading @@ -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) { Loading