Loading services/core/java/com/android/server/vibrator/Vibration.java +6 −2 Original line number Diff line number Diff line Loading @@ -47,12 +47,16 @@ final class Vibration { FINISHED, FINISHED_UNEXPECTED, // Didn't terminate in the usual way. FORWARDED_TO_INPUT_DEVICES, CANCELLED, CANCELLED_BINDER_DIED, CANCELLED_BY_SCREEN_OFF, CANCELLED_BY_SETTINGS_UPDATE, CANCELLED_BY_USER, CANCELLED_BY_UNKNOWN_REASON, CANCELLED_SUPERSEDED, IGNORED_ERROR_APP_OPS, IGNORED_ERROR_CANCELLING, IGNORED_ERROR_SCHEDULING, IGNORED_ERROR_TOKEN, IGNORED, IGNORED_APP_OPS, IGNORED_BACKGROUND, IGNORED_UNKNOWN_VIBRATION, Loading services/core/java/com/android/server/vibrator/VibrationStepConductor.java +45 −23 Original line number Diff line number Diff line Loading @@ -79,12 +79,14 @@ final class VibrationStepConductor implements IBinder.DeathRecipient { private final Object mLock = new Object(); @GuardedBy("mLock") private final IntArray mSignalVibratorsComplete; @Nullable @GuardedBy("mLock") private boolean mSignalCancel = false; private Vibration.Status mSignalCancelStatus = null; @GuardedBy("mLock") private boolean mSignalCancelImmediate = false; private boolean mCancelled = false; @Nullable private Vibration.Status mCancelStatus = null; private boolean mCancelledImmediately = false; // hard stop private int mPendingVibrateSteps; private int mRemainingStartSequentialEffectSteps; Loading Loading @@ -185,8 +187,8 @@ final class VibrationStepConductor implements IBinder.DeathRecipient { expectIsVibrationThread(true); } if (mCancelled) { return Vibration.Status.CANCELLED; if (mCancelStatus != null) { return mCancelStatus; } if (mPendingVibrateSteps > 0 || mRemainingStartSequentialEffectSteps > 0) { Loading Loading @@ -303,7 +305,7 @@ final class VibrationStepConductor implements IBinder.DeathRecipient { if (DEBUG) { Slog.d(TAG, "Binder died, cancelling vibration..."); } notifyCancelled(/* immediate= */ false); notifyCancelled(Vibration.Status.CANCELLED_BINDER_DIED, /* immediate= */ false); } /** Loading @@ -312,21 +314,40 @@ final class VibrationStepConductor implements IBinder.DeathRecipient { * * @param immediate indicates whether cancellation should abort urgently and skip cleanup steps. */ public void notifyCancelled(boolean immediate) { public void notifyCancelled(@NonNull Vibration.Status cancelStatus, boolean immediate) { if (Build.IS_DEBUGGABLE) { expectIsVibrationThread(false); } if (DEBUG) { Slog.d(TAG, "Vibration cancel requested with status=" + cancelStatus + ", immediate=" + immediate); } if ((cancelStatus == null) || !cancelStatus.name().startsWith("CANCEL")) { Slog.w(TAG, "Vibration cancel requested with bad status=" + cancelStatus + ", using CANCELLED_UNKNOWN_REASON to ensure cancellation."); cancelStatus = Vibration.Status.CANCELLED_BY_UNKNOWN_REASON; } synchronized (mLock) { if (immediate && mSignalCancelImmediate || mSignalCancel) { // Nothing to update: already cancelled previously. if (immediate && mSignalCancelImmediate || (mSignalCancelStatus != null)) { if (DEBUG) { Slog.d(TAG, "Vibration cancel request ignored as the vibration " + mVibration.id + "is already being cancelled with status=" + mSignalCancelStatus + ", immediate=" + mSignalCancelImmediate); } return; } mSignalCancelImmediate |= immediate; mSignalCancel = true; mLock.notify(); } if (mSignalCancelStatus == null) { mSignalCancelStatus = cancelStatus; } else { if (DEBUG) { Slog.d(TAG, "Vibration cancel requested, immediate=" + immediate); Slog.d(TAG, "Vibration cancel request new status=" + cancelStatus + " ignored as the vibration was already cancelled with status=" + mSignalCancelStatus + ", but immediate flag was updated to " + mSignalCancelImmediate); } } mLock.notify(); } } Loading Loading @@ -380,7 +401,7 @@ final class VibrationStepConductor implements IBinder.DeathRecipient { if (Build.IS_DEBUGGABLE) { expectIsVibrationThread(true); // Reads VibrationThread variables as well as signals. } return (mSignalCancel && !mCancelled) return (mSignalCancelStatus != mCancelStatus) || (mSignalCancelImmediate && !mCancelledImmediately) || (mSignalVibratorsComplete.size() > 0); } Loading @@ -395,7 +416,7 @@ final class VibrationStepConductor implements IBinder.DeathRecipient { } int[] vibratorsToProcess = null; boolean doCancel = false; Vibration.Status doCancelStatus = null; boolean doCancelImmediate = false; // Collect signals to process, but don't keep the lock while processing them. synchronized (mLock) { Loading @@ -405,9 +426,10 @@ final class VibrationStepConductor implements IBinder.DeathRecipient { } // This should only happen once. doCancelImmediate = true; doCancelStatus = mSignalCancelStatus; } if (mSignalCancel && !mCancelled) { doCancel = true; if (mSignalCancelStatus != mCancelStatus) { doCancelStatus = mSignalCancelStatus; } if (!doCancelImmediate && mSignalVibratorsComplete.size() > 0) { // Swap out the queue of completions to process. Loading @@ -421,11 +443,11 @@ final class VibrationStepConductor implements IBinder.DeathRecipient { // completion signals that were collected in this call, but we won't process them // anyway as all steps are cancelled. if (doCancelImmediate) { processCancelImmediately(); processCancelImmediately(doCancelStatus); return; } if (doCancel) { processCancel(); if (doCancelStatus != null) { processCancel(doCancelStatus); } if (vibratorsToProcess != null) { processVibratorsComplete(vibratorsToProcess); Loading @@ -438,12 +460,12 @@ final class VibrationStepConductor implements IBinder.DeathRecipient { * <p>This will remove all steps and replace them with respective results of * {@link Step#cancel()}. */ public void processCancel() { public void processCancel(Vibration.Status cancelStatus) { if (Build.IS_DEBUGGABLE) { expectIsVibrationThread(true); } mCancelled = true; mCancelStatus = cancelStatus; // 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<>(); Loading @@ -461,13 +483,13 @@ final class VibrationStepConductor implements IBinder.DeathRecipient { * * <p>This will remove and trigger {@link Step#cancelImmediately()} in all steps, in order. */ public void processCancelImmediately() { public void processCancelImmediately(Vibration.Status cancelStatus) { if (Build.IS_DEBUGGABLE) { expectIsVibrationThread(true); } mCancelledImmediately = true; mCancelled = true; mCancelStatus = cancelStatus; Step step; while ((step = pollNext()) != null) { step.cancelImmediately(); Loading services/core/java/com/android/server/vibrator/VibratorManagerService.java +15 −10 Original line number Diff line number Diff line Loading @@ -162,10 +162,11 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { // When the system is entering a non-interactive state, we want to cancel // vibrations in case a misbehaving app has abandoned them. if (shouldCancelOnScreenOffLocked(mNextVibration)) { clearNextVibrationLocked(Vibration.Status.CANCELLED); clearNextVibrationLocked(Vibration.Status.CANCELLED_BY_SCREEN_OFF); } if (shouldCancelOnScreenOffLocked(mCurrentVibration)) { mCurrentVibration.notifyCancelled(/* immediate= */ false); mCurrentVibration.notifyCancelled(Vibration.Status.CANCELLED_BY_SCREEN_OFF, /* immediate= */ false); } } } Loading Loading @@ -426,7 +427,8 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { final long ident = Binder.clearCallingIdentity(); try { if (mCurrentVibration != null) { mCurrentVibration.notifyCancelled(/* immediate= */ false); mCurrentVibration.notifyCancelled(Vibration.Status.CANCELLED_SUPERSEDED, /* immediate= */ false); } Vibration.Status status = startVibrationLocked(vib); if (status != Vibration.Status.RUNNING) { Loading Loading @@ -459,19 +461,20 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { if (mNextVibration != null && shouldCancelVibration(mNextVibration.getVibration(), usageFilter, token)) { clearNextVibrationLocked(Vibration.Status.CANCELLED); clearNextVibrationLocked(Vibration.Status.CANCELLED_BY_USER); } if (mCurrentVibration != null && shouldCancelVibration(mCurrentVibration.getVibration(), usageFilter, token)) { mCurrentVibration.notifyCancelled(/* immediate= */false); mCurrentVibration.notifyCancelled(Vibration.Status.CANCELLED_BY_USER, /* immediate= */false); } if (mCurrentExternalVibration != null && shouldCancelVibration( mCurrentExternalVibration.externalVibration.getVibrationAttributes(), usageFilter)) { mCurrentExternalVibration.externalVibration.mute(); endExternalVibrateLocked(Vibration.Status.CANCELLED, endExternalVibrateLocked(Vibration.Status.CANCELLED_BY_USER, /* continueExternalControl= */ false); } } finally { Loading Loading @@ -600,7 +603,8 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { Slog.d(TAG, "Canceling vibration because settings changed: " + (inputDevicesChanged ? "input devices changed" : ignoreStatus)); } mCurrentVibration.notifyCancelled(/* immediate= */ false); mCurrentVibration.notifyCancelled(Vibration.Status.CANCELLED_BY_SETTINGS_UPDATE, /* immediate= */ false); } } } Loading Loading @@ -1319,7 +1323,7 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { if (DEBUG) { Slog.d(TAG, "External vibration finished because binder died"); } endExternalVibrateLocked(Vibration.Status.CANCELLED, endExternalVibrateLocked(Vibration.Status.CANCELLED_BINDER_DIED, /* continueExternalControl= */ false); } } Loading Loading @@ -1543,7 +1547,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.notifyCancelled(/* immediate= */ true); mCurrentVibration.notifyCancelled(Vibration.Status.CANCELLED_SUPERSEDED, /* immediate= */ true); waitForCompletion = true; } } else { Loading @@ -1557,7 +1562,7 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { // would need to mute the old one still if it came from a different controller. alreadyUnderExternalControl = true; mCurrentExternalVibration.externalVibration.mute(); endExternalVibrateLocked(Vibration.Status.CANCELLED, endExternalVibrateLocked(Vibration.Status.CANCELLED_SUPERSEDED, /* continueExternalControl= */ true); } mCurrentExternalVibration = new ExternalVibrationHolder(vib); Loading services/tests/servicestests/src/com/android/server/vibrator/VibrationThreadTest.java +34 −28 Original line number Diff line number Diff line Loading @@ -257,13 +257,13 @@ public class VibrationThreadTest { assertTrue(mThread.isRunningVibrationId(vibrationId)); assertTrue(mControllers.get(VIBRATOR_ID).isVibrating()); conductor.notifyCancelled(/* immediate= */ false); conductor.notifyCancelled(Vibration.Status.CANCELLED_SUPERSEDED, /* immediate= */ false); waitForCompletion(); assertFalse(mThread.isRunningVibrationId(vibrationId)); verify(mManagerHooks).noteVibratorOn(eq(UID), anyLong()); verify(mManagerHooks).noteVibratorOff(eq(UID)); verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED); verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED_SUPERSEDED); assertFalse(mControllers.get(VIBRATOR_ID).isVibrating()); List<Float> playedAmplitudes = fakeVibrator.getAmplitudes(); Loading @@ -288,10 +288,10 @@ public class VibrationThreadTest { VibrationStepConductor conductor = startThreadAndDispatcher(vibrationId, effect); assertTrue(waitUntil(() -> !fakeVibrator.getAmplitudes().isEmpty(), TEST_TIMEOUT_MILLIS)); conductor.notifyCancelled(/* immediate= */ false); conductor.notifyCancelled(Vibration.Status.CANCELLED_BY_USER, /* immediate= */ false); waitForCompletion(); verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED); verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED_BY_USER); assertFalse(mControllers.get(VIBRATOR_ID).isVibrating()); assertEquals(Arrays.asList(expectedOneShot(1000)), fakeVibrator.getEffectSegments(vibrationId)); Loading @@ -310,10 +310,10 @@ public class VibrationThreadTest { VibrationStepConductor conductor = startThreadAndDispatcher(vibrationId, effect); assertTrue(waitUntil(() -> !fakeVibrator.getAmplitudes().isEmpty(), TEST_TIMEOUT_MILLIS)); conductor.notifyCancelled(/* immediate= */ false); conductor.notifyCancelled(Vibration.Status.CANCELLED_BY_USER, /* immediate= */ false); waitForCompletion(); verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED); verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED_BY_USER); assertFalse(mControllers.get(VIBRATOR_ID).isVibrating()); assertEquals(Arrays.asList(expectedOneShot(5550)), fakeVibrator.getEffectSegments(vibrationId)); Loading @@ -334,10 +334,10 @@ public class VibrationThreadTest { assertTrue(waitUntil(() -> fakeVibrator.getAmplitudes().size() > 2 * amplitudes.length, 1000 + TEST_TIMEOUT_MILLIS)); conductor.notifyCancelled(/* immediate= */ false); conductor.notifyCancelled(Vibration.Status.CANCELLED_BY_USER, /* immediate= */ false); waitForCompletion(); verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED); verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED_BY_USER); assertFalse(mControllers.get(VIBRATOR_ID).isVibrating()); assertEquals(2, fakeVibrator.getEffectSegments(vibrationId).size()); // First time turn vibrator ON for minimum of 1s. Loading Loading @@ -371,13 +371,14 @@ public class VibrationThreadTest { // Run cancel in a separate thread so if VibrationThread.cancel blocks then this test should // fail at waitForCompletion(vibrationThread) if the vibration not cancelled immediately. Thread cancellingThread = new Thread(() -> conductor.notifyCancelled(/* immediate= */ false)); new Thread(() -> conductor.notifyCancelled( Vibration.Status.CANCELLED_BY_SETTINGS_UPDATE, /* immediate= */ false)); cancellingThread.start(); waitForCompletion(/* timeout= */ 50); cancellingThread.join(); verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED); verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED_BY_SETTINGS_UPDATE); assertFalse(mControllers.get(VIBRATOR_ID).isVibrating()); } Loading @@ -397,13 +398,14 @@ public class VibrationThreadTest { // Run cancel in a separate thread so if VibrationThread.cancel blocks then this test should // fail at waitForCompletion(vibrationThread) if the vibration not cancelled immediately. Thread cancellingThread = new Thread(() -> conductor.notifyCancelled(/* immediate= */ false)); new Thread(() -> conductor.notifyCancelled( Vibration.Status.CANCELLED_BY_SCREEN_OFF, /* immediate= */ false)); cancellingThread.start(); waitForCompletion(/* timeout= */ 50); cancellingThread.join(); verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED); verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED_BY_SCREEN_OFF); assertFalse(mControllers.get(VIBRATOR_ID).isVibrating()); } Loading Loading @@ -647,7 +649,7 @@ public class VibrationThreadTest { waitForCompletion(); assertFalse(mControllers.get(VIBRATOR_ID).isVibrating()); verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED); verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED_BINDER_DIED); } @Test Loading Loading @@ -1043,7 +1045,8 @@ public class VibrationThreadTest { // Run cancel in a separate thread so if VibrationThread.cancel blocks then this test should // fail at waitForCompletion(cancellingThread). Thread cancellingThread = new Thread( () -> conductor.notifyCancelled(/* immediate= */ false)); () -> conductor.notifyCancelled( Vibration.Status.CANCELLED_BY_USER, /* immediate= */ false)); cancellingThread.start(); // Cancelling the vibration should be fast and return right away, even if the thread is Loading @@ -1052,7 +1055,7 @@ public class VibrationThreadTest { // After the vibrator call ends the vibration is cancelled and the vibrator is turned off. waitForCompletion(/* timeout= */ latency + TEST_TIMEOUT_MILLIS); verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED); verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED_BY_USER); assertFalse(mControllers.get(VIBRATOR_ID).isVibrating()); } Loading Loading @@ -1080,13 +1083,14 @@ public class VibrationThreadTest { // Run cancel in a separate thread so if VibrationThread.cancel blocks then this test should // fail at waitForCompletion(vibrationThread) if the vibration not cancelled immediately. Thread cancellingThread = new Thread( () -> conductor.notifyCancelled(/* immediate= */ false)); () -> conductor.notifyCancelled( Vibration.Status.CANCELLED_SUPERSEDED, /* immediate= */ false)); cancellingThread.start(); waitForCompletion(/* timeout= */ 50); cancellingThread.join(); verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED); verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED_SUPERSEDED); assertFalse(mControllers.get(1).isVibrating()); assertFalse(mControllers.get(2).isVibrating()); } Loading @@ -1113,13 +1117,14 @@ public class VibrationThreadTest { // Run cancel in a separate thread so if VibrationThread.cancel blocks then this test should // fail at waitForCompletion(vibrationThread) if the vibration not cancelled immediately. Thread cancellingThread = new Thread(() -> conductor.notifyCancelled(/* immediate= */ false)); new Thread(() -> conductor.notifyCancelled( Vibration.Status.CANCELLED_BY_SCREEN_OFF, /* immediate= */ false)); cancellingThread.start(); waitForCompletion(/* timeout= */ 50); cancellingThread.join(); verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED); verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED_BY_SCREEN_OFF); assertFalse(mControllers.get(1).isVibrating()); assertFalse(mControllers.get(2).isVibrating()); } Loading @@ -1139,7 +1144,7 @@ public class VibrationThreadTest { verify(mVibrationToken).linkToDeath(same(conductor), eq(0)); verify(mVibrationToken).unlinkToDeath(same(conductor), eq(0)); verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED); verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED_BINDER_DIED); assertFalse(mVibratorProviders.get(VIBRATOR_ID).getEffectSegments(vibrationId).isEmpty()); assertFalse(mControllers.get(VIBRATOR_ID).isVibrating()); } Loading Loading @@ -1193,12 +1198,13 @@ public class VibrationThreadTest { mVibratorProviders.get(VIBRATOR_ID).getEffectSegments(vibrationId)); // Will stop the ramp down right away. conductor.notifyCancelled(/* immediate= */ true); conductor.notifyCancelled( Vibration.Status.CANCELLED_BY_SETTINGS_UPDATE, /* immediate= */ true); waitForCompletion(); // Does not cancel already finished vibration, but releases vibrator. verify(mManagerHooks, never()).onVibrationCompleted(eq(vibrationId), eq(Vibration.Status.CANCELLED)); eq(Vibration.Status.CANCELLED_BY_SETTINGS_UPDATE)); verify(mManagerHooks).onVibrationThreadReleased(vibrationId); } Loading @@ -1214,10 +1220,10 @@ public class VibrationThreadTest { VibrationStepConductor conductor = startThreadAndDispatcher(vibrationId, effect); assertTrue(waitUntil(() -> mControllers.get(VIBRATOR_ID).isVibrating(), TEST_TIMEOUT_MILLIS)); conductor.notifyCancelled(/* immediate= */ false); conductor.notifyCancelled(Vibration.Status.CANCELLED_BY_USER, /* immediate= */ false); waitForCompletion(); verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED); verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED_BY_USER); // Duration extended for 10000 + 15. assertEquals(Arrays.asList(expectedOneShot(10_015)), Loading Loading @@ -1337,7 +1343,7 @@ public class VibrationThreadTest { VibrationStepConductor conductor2 = startThreadAndDispatcher(vibrationId2, effect2); // Effect2 won't complete on its own. Cancel it after a couple of repeats. Thread.sleep(150); // More than two TICKs. conductor2.notifyCancelled(/* immediate= */ false); conductor2.notifyCancelled(Vibration.Status.CANCELLED_BY_USER, /* immediate= */ false); waitForCompletion(); startThreadAndDispatcher(vibrationId3, effect3); Loading @@ -1346,7 +1352,7 @@ public class VibrationThreadTest { // Effect4 is a long oneshot, but it gets cancelled as fast as possible. long start4 = System.currentTimeMillis(); VibrationStepConductor conductor4 = startThreadAndDispatcher(vibrationId4, effect4); conductor4.notifyCancelled(/* immediate= */ true); conductor4.notifyCancelled(Vibration.Status.CANCELLED_SUPERSEDED, /* immediate= */ true); waitForCompletion(); long duration4 = System.currentTimeMillis() - start4; Loading @@ -1366,7 +1372,7 @@ public class VibrationThreadTest { // Effect2: repeating, cancelled. verify(mControllerCallbacks, atLeast(2)).onComplete(VIBRATOR_ID, vibrationId2); verifyCallbacksTriggered(vibrationId2, Vibration.Status.CANCELLED); verifyCallbacksTriggered(vibrationId2, Vibration.Status.CANCELLED_BY_USER); // The exact count of segments might vary, so just check that there's more than 2 and // all elements are the same segment. Loading @@ -1384,7 +1390,7 @@ public class VibrationThreadTest { fakeVibrator.getEffectSegments(vibrationId3)); // Effect4: cancelled quickly. verifyCallbacksTriggered(vibrationId4, Vibration.Status.CANCELLED); verifyCallbacksTriggered(vibrationId4, Vibration.Status.CANCELLED_SUPERSEDED); assertTrue("Tested duration=" + duration4, duration4 < 2000); // Effect5: normal oneshot. Don't worry about amplitude, as effect4 may or may not have Loading Loading
services/core/java/com/android/server/vibrator/Vibration.java +6 −2 Original line number Diff line number Diff line Loading @@ -47,12 +47,16 @@ final class Vibration { FINISHED, FINISHED_UNEXPECTED, // Didn't terminate in the usual way. FORWARDED_TO_INPUT_DEVICES, CANCELLED, CANCELLED_BINDER_DIED, CANCELLED_BY_SCREEN_OFF, CANCELLED_BY_SETTINGS_UPDATE, CANCELLED_BY_USER, CANCELLED_BY_UNKNOWN_REASON, CANCELLED_SUPERSEDED, IGNORED_ERROR_APP_OPS, IGNORED_ERROR_CANCELLING, IGNORED_ERROR_SCHEDULING, IGNORED_ERROR_TOKEN, IGNORED, IGNORED_APP_OPS, IGNORED_BACKGROUND, IGNORED_UNKNOWN_VIBRATION, Loading
services/core/java/com/android/server/vibrator/VibrationStepConductor.java +45 −23 Original line number Diff line number Diff line Loading @@ -79,12 +79,14 @@ final class VibrationStepConductor implements IBinder.DeathRecipient { private final Object mLock = new Object(); @GuardedBy("mLock") private final IntArray mSignalVibratorsComplete; @Nullable @GuardedBy("mLock") private boolean mSignalCancel = false; private Vibration.Status mSignalCancelStatus = null; @GuardedBy("mLock") private boolean mSignalCancelImmediate = false; private boolean mCancelled = false; @Nullable private Vibration.Status mCancelStatus = null; private boolean mCancelledImmediately = false; // hard stop private int mPendingVibrateSteps; private int mRemainingStartSequentialEffectSteps; Loading Loading @@ -185,8 +187,8 @@ final class VibrationStepConductor implements IBinder.DeathRecipient { expectIsVibrationThread(true); } if (mCancelled) { return Vibration.Status.CANCELLED; if (mCancelStatus != null) { return mCancelStatus; } if (mPendingVibrateSteps > 0 || mRemainingStartSequentialEffectSteps > 0) { Loading Loading @@ -303,7 +305,7 @@ final class VibrationStepConductor implements IBinder.DeathRecipient { if (DEBUG) { Slog.d(TAG, "Binder died, cancelling vibration..."); } notifyCancelled(/* immediate= */ false); notifyCancelled(Vibration.Status.CANCELLED_BINDER_DIED, /* immediate= */ false); } /** Loading @@ -312,21 +314,40 @@ final class VibrationStepConductor implements IBinder.DeathRecipient { * * @param immediate indicates whether cancellation should abort urgently and skip cleanup steps. */ public void notifyCancelled(boolean immediate) { public void notifyCancelled(@NonNull Vibration.Status cancelStatus, boolean immediate) { if (Build.IS_DEBUGGABLE) { expectIsVibrationThread(false); } if (DEBUG) { Slog.d(TAG, "Vibration cancel requested with status=" + cancelStatus + ", immediate=" + immediate); } if ((cancelStatus == null) || !cancelStatus.name().startsWith("CANCEL")) { Slog.w(TAG, "Vibration cancel requested with bad status=" + cancelStatus + ", using CANCELLED_UNKNOWN_REASON to ensure cancellation."); cancelStatus = Vibration.Status.CANCELLED_BY_UNKNOWN_REASON; } synchronized (mLock) { if (immediate && mSignalCancelImmediate || mSignalCancel) { // Nothing to update: already cancelled previously. if (immediate && mSignalCancelImmediate || (mSignalCancelStatus != null)) { if (DEBUG) { Slog.d(TAG, "Vibration cancel request ignored as the vibration " + mVibration.id + "is already being cancelled with status=" + mSignalCancelStatus + ", immediate=" + mSignalCancelImmediate); } return; } mSignalCancelImmediate |= immediate; mSignalCancel = true; mLock.notify(); } if (mSignalCancelStatus == null) { mSignalCancelStatus = cancelStatus; } else { if (DEBUG) { Slog.d(TAG, "Vibration cancel requested, immediate=" + immediate); Slog.d(TAG, "Vibration cancel request new status=" + cancelStatus + " ignored as the vibration was already cancelled with status=" + mSignalCancelStatus + ", but immediate flag was updated to " + mSignalCancelImmediate); } } mLock.notify(); } } Loading Loading @@ -380,7 +401,7 @@ final class VibrationStepConductor implements IBinder.DeathRecipient { if (Build.IS_DEBUGGABLE) { expectIsVibrationThread(true); // Reads VibrationThread variables as well as signals. } return (mSignalCancel && !mCancelled) return (mSignalCancelStatus != mCancelStatus) || (mSignalCancelImmediate && !mCancelledImmediately) || (mSignalVibratorsComplete.size() > 0); } Loading @@ -395,7 +416,7 @@ final class VibrationStepConductor implements IBinder.DeathRecipient { } int[] vibratorsToProcess = null; boolean doCancel = false; Vibration.Status doCancelStatus = null; boolean doCancelImmediate = false; // Collect signals to process, but don't keep the lock while processing them. synchronized (mLock) { Loading @@ -405,9 +426,10 @@ final class VibrationStepConductor implements IBinder.DeathRecipient { } // This should only happen once. doCancelImmediate = true; doCancelStatus = mSignalCancelStatus; } if (mSignalCancel && !mCancelled) { doCancel = true; if (mSignalCancelStatus != mCancelStatus) { doCancelStatus = mSignalCancelStatus; } if (!doCancelImmediate && mSignalVibratorsComplete.size() > 0) { // Swap out the queue of completions to process. Loading @@ -421,11 +443,11 @@ final class VibrationStepConductor implements IBinder.DeathRecipient { // completion signals that were collected in this call, but we won't process them // anyway as all steps are cancelled. if (doCancelImmediate) { processCancelImmediately(); processCancelImmediately(doCancelStatus); return; } if (doCancel) { processCancel(); if (doCancelStatus != null) { processCancel(doCancelStatus); } if (vibratorsToProcess != null) { processVibratorsComplete(vibratorsToProcess); Loading @@ -438,12 +460,12 @@ final class VibrationStepConductor implements IBinder.DeathRecipient { * <p>This will remove all steps and replace them with respective results of * {@link Step#cancel()}. */ public void processCancel() { public void processCancel(Vibration.Status cancelStatus) { if (Build.IS_DEBUGGABLE) { expectIsVibrationThread(true); } mCancelled = true; mCancelStatus = cancelStatus; // 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<>(); Loading @@ -461,13 +483,13 @@ final class VibrationStepConductor implements IBinder.DeathRecipient { * * <p>This will remove and trigger {@link Step#cancelImmediately()} in all steps, in order. */ public void processCancelImmediately() { public void processCancelImmediately(Vibration.Status cancelStatus) { if (Build.IS_DEBUGGABLE) { expectIsVibrationThread(true); } mCancelledImmediately = true; mCancelled = true; mCancelStatus = cancelStatus; Step step; while ((step = pollNext()) != null) { step.cancelImmediately(); Loading
services/core/java/com/android/server/vibrator/VibratorManagerService.java +15 −10 Original line number Diff line number Diff line Loading @@ -162,10 +162,11 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { // When the system is entering a non-interactive state, we want to cancel // vibrations in case a misbehaving app has abandoned them. if (shouldCancelOnScreenOffLocked(mNextVibration)) { clearNextVibrationLocked(Vibration.Status.CANCELLED); clearNextVibrationLocked(Vibration.Status.CANCELLED_BY_SCREEN_OFF); } if (shouldCancelOnScreenOffLocked(mCurrentVibration)) { mCurrentVibration.notifyCancelled(/* immediate= */ false); mCurrentVibration.notifyCancelled(Vibration.Status.CANCELLED_BY_SCREEN_OFF, /* immediate= */ false); } } } Loading Loading @@ -426,7 +427,8 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { final long ident = Binder.clearCallingIdentity(); try { if (mCurrentVibration != null) { mCurrentVibration.notifyCancelled(/* immediate= */ false); mCurrentVibration.notifyCancelled(Vibration.Status.CANCELLED_SUPERSEDED, /* immediate= */ false); } Vibration.Status status = startVibrationLocked(vib); if (status != Vibration.Status.RUNNING) { Loading Loading @@ -459,19 +461,20 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { if (mNextVibration != null && shouldCancelVibration(mNextVibration.getVibration(), usageFilter, token)) { clearNextVibrationLocked(Vibration.Status.CANCELLED); clearNextVibrationLocked(Vibration.Status.CANCELLED_BY_USER); } if (mCurrentVibration != null && shouldCancelVibration(mCurrentVibration.getVibration(), usageFilter, token)) { mCurrentVibration.notifyCancelled(/* immediate= */false); mCurrentVibration.notifyCancelled(Vibration.Status.CANCELLED_BY_USER, /* immediate= */false); } if (mCurrentExternalVibration != null && shouldCancelVibration( mCurrentExternalVibration.externalVibration.getVibrationAttributes(), usageFilter)) { mCurrentExternalVibration.externalVibration.mute(); endExternalVibrateLocked(Vibration.Status.CANCELLED, endExternalVibrateLocked(Vibration.Status.CANCELLED_BY_USER, /* continueExternalControl= */ false); } } finally { Loading Loading @@ -600,7 +603,8 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { Slog.d(TAG, "Canceling vibration because settings changed: " + (inputDevicesChanged ? "input devices changed" : ignoreStatus)); } mCurrentVibration.notifyCancelled(/* immediate= */ false); mCurrentVibration.notifyCancelled(Vibration.Status.CANCELLED_BY_SETTINGS_UPDATE, /* immediate= */ false); } } } Loading Loading @@ -1319,7 +1323,7 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { if (DEBUG) { Slog.d(TAG, "External vibration finished because binder died"); } endExternalVibrateLocked(Vibration.Status.CANCELLED, endExternalVibrateLocked(Vibration.Status.CANCELLED_BINDER_DIED, /* continueExternalControl= */ false); } } Loading Loading @@ -1543,7 +1547,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.notifyCancelled(/* immediate= */ true); mCurrentVibration.notifyCancelled(Vibration.Status.CANCELLED_SUPERSEDED, /* immediate= */ true); waitForCompletion = true; } } else { Loading @@ -1557,7 +1562,7 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { // would need to mute the old one still if it came from a different controller. alreadyUnderExternalControl = true; mCurrentExternalVibration.externalVibration.mute(); endExternalVibrateLocked(Vibration.Status.CANCELLED, endExternalVibrateLocked(Vibration.Status.CANCELLED_SUPERSEDED, /* continueExternalControl= */ true); } mCurrentExternalVibration = new ExternalVibrationHolder(vib); Loading
services/tests/servicestests/src/com/android/server/vibrator/VibrationThreadTest.java +34 −28 Original line number Diff line number Diff line Loading @@ -257,13 +257,13 @@ public class VibrationThreadTest { assertTrue(mThread.isRunningVibrationId(vibrationId)); assertTrue(mControllers.get(VIBRATOR_ID).isVibrating()); conductor.notifyCancelled(/* immediate= */ false); conductor.notifyCancelled(Vibration.Status.CANCELLED_SUPERSEDED, /* immediate= */ false); waitForCompletion(); assertFalse(mThread.isRunningVibrationId(vibrationId)); verify(mManagerHooks).noteVibratorOn(eq(UID), anyLong()); verify(mManagerHooks).noteVibratorOff(eq(UID)); verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED); verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED_SUPERSEDED); assertFalse(mControllers.get(VIBRATOR_ID).isVibrating()); List<Float> playedAmplitudes = fakeVibrator.getAmplitudes(); Loading @@ -288,10 +288,10 @@ public class VibrationThreadTest { VibrationStepConductor conductor = startThreadAndDispatcher(vibrationId, effect); assertTrue(waitUntil(() -> !fakeVibrator.getAmplitudes().isEmpty(), TEST_TIMEOUT_MILLIS)); conductor.notifyCancelled(/* immediate= */ false); conductor.notifyCancelled(Vibration.Status.CANCELLED_BY_USER, /* immediate= */ false); waitForCompletion(); verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED); verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED_BY_USER); assertFalse(mControllers.get(VIBRATOR_ID).isVibrating()); assertEquals(Arrays.asList(expectedOneShot(1000)), fakeVibrator.getEffectSegments(vibrationId)); Loading @@ -310,10 +310,10 @@ public class VibrationThreadTest { VibrationStepConductor conductor = startThreadAndDispatcher(vibrationId, effect); assertTrue(waitUntil(() -> !fakeVibrator.getAmplitudes().isEmpty(), TEST_TIMEOUT_MILLIS)); conductor.notifyCancelled(/* immediate= */ false); conductor.notifyCancelled(Vibration.Status.CANCELLED_BY_USER, /* immediate= */ false); waitForCompletion(); verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED); verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED_BY_USER); assertFalse(mControllers.get(VIBRATOR_ID).isVibrating()); assertEquals(Arrays.asList(expectedOneShot(5550)), fakeVibrator.getEffectSegments(vibrationId)); Loading @@ -334,10 +334,10 @@ public class VibrationThreadTest { assertTrue(waitUntil(() -> fakeVibrator.getAmplitudes().size() > 2 * amplitudes.length, 1000 + TEST_TIMEOUT_MILLIS)); conductor.notifyCancelled(/* immediate= */ false); conductor.notifyCancelled(Vibration.Status.CANCELLED_BY_USER, /* immediate= */ false); waitForCompletion(); verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED); verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED_BY_USER); assertFalse(mControllers.get(VIBRATOR_ID).isVibrating()); assertEquals(2, fakeVibrator.getEffectSegments(vibrationId).size()); // First time turn vibrator ON for minimum of 1s. Loading Loading @@ -371,13 +371,14 @@ public class VibrationThreadTest { // Run cancel in a separate thread so if VibrationThread.cancel blocks then this test should // fail at waitForCompletion(vibrationThread) if the vibration not cancelled immediately. Thread cancellingThread = new Thread(() -> conductor.notifyCancelled(/* immediate= */ false)); new Thread(() -> conductor.notifyCancelled( Vibration.Status.CANCELLED_BY_SETTINGS_UPDATE, /* immediate= */ false)); cancellingThread.start(); waitForCompletion(/* timeout= */ 50); cancellingThread.join(); verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED); verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED_BY_SETTINGS_UPDATE); assertFalse(mControllers.get(VIBRATOR_ID).isVibrating()); } Loading @@ -397,13 +398,14 @@ public class VibrationThreadTest { // Run cancel in a separate thread so if VibrationThread.cancel blocks then this test should // fail at waitForCompletion(vibrationThread) if the vibration not cancelled immediately. Thread cancellingThread = new Thread(() -> conductor.notifyCancelled(/* immediate= */ false)); new Thread(() -> conductor.notifyCancelled( Vibration.Status.CANCELLED_BY_SCREEN_OFF, /* immediate= */ false)); cancellingThread.start(); waitForCompletion(/* timeout= */ 50); cancellingThread.join(); verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED); verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED_BY_SCREEN_OFF); assertFalse(mControllers.get(VIBRATOR_ID).isVibrating()); } Loading Loading @@ -647,7 +649,7 @@ public class VibrationThreadTest { waitForCompletion(); assertFalse(mControllers.get(VIBRATOR_ID).isVibrating()); verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED); verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED_BINDER_DIED); } @Test Loading Loading @@ -1043,7 +1045,8 @@ public class VibrationThreadTest { // Run cancel in a separate thread so if VibrationThread.cancel blocks then this test should // fail at waitForCompletion(cancellingThread). Thread cancellingThread = new Thread( () -> conductor.notifyCancelled(/* immediate= */ false)); () -> conductor.notifyCancelled( Vibration.Status.CANCELLED_BY_USER, /* immediate= */ false)); cancellingThread.start(); // Cancelling the vibration should be fast and return right away, even if the thread is Loading @@ -1052,7 +1055,7 @@ public class VibrationThreadTest { // After the vibrator call ends the vibration is cancelled and the vibrator is turned off. waitForCompletion(/* timeout= */ latency + TEST_TIMEOUT_MILLIS); verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED); verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED_BY_USER); assertFalse(mControllers.get(VIBRATOR_ID).isVibrating()); } Loading Loading @@ -1080,13 +1083,14 @@ public class VibrationThreadTest { // Run cancel in a separate thread so if VibrationThread.cancel blocks then this test should // fail at waitForCompletion(vibrationThread) if the vibration not cancelled immediately. Thread cancellingThread = new Thread( () -> conductor.notifyCancelled(/* immediate= */ false)); () -> conductor.notifyCancelled( Vibration.Status.CANCELLED_SUPERSEDED, /* immediate= */ false)); cancellingThread.start(); waitForCompletion(/* timeout= */ 50); cancellingThread.join(); verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED); verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED_SUPERSEDED); assertFalse(mControllers.get(1).isVibrating()); assertFalse(mControllers.get(2).isVibrating()); } Loading @@ -1113,13 +1117,14 @@ public class VibrationThreadTest { // Run cancel in a separate thread so if VibrationThread.cancel blocks then this test should // fail at waitForCompletion(vibrationThread) if the vibration not cancelled immediately. Thread cancellingThread = new Thread(() -> conductor.notifyCancelled(/* immediate= */ false)); new Thread(() -> conductor.notifyCancelled( Vibration.Status.CANCELLED_BY_SCREEN_OFF, /* immediate= */ false)); cancellingThread.start(); waitForCompletion(/* timeout= */ 50); cancellingThread.join(); verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED); verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED_BY_SCREEN_OFF); assertFalse(mControllers.get(1).isVibrating()); assertFalse(mControllers.get(2).isVibrating()); } Loading @@ -1139,7 +1144,7 @@ public class VibrationThreadTest { verify(mVibrationToken).linkToDeath(same(conductor), eq(0)); verify(mVibrationToken).unlinkToDeath(same(conductor), eq(0)); verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED); verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED_BINDER_DIED); assertFalse(mVibratorProviders.get(VIBRATOR_ID).getEffectSegments(vibrationId).isEmpty()); assertFalse(mControllers.get(VIBRATOR_ID).isVibrating()); } Loading Loading @@ -1193,12 +1198,13 @@ public class VibrationThreadTest { mVibratorProviders.get(VIBRATOR_ID).getEffectSegments(vibrationId)); // Will stop the ramp down right away. conductor.notifyCancelled(/* immediate= */ true); conductor.notifyCancelled( Vibration.Status.CANCELLED_BY_SETTINGS_UPDATE, /* immediate= */ true); waitForCompletion(); // Does not cancel already finished vibration, but releases vibrator. verify(mManagerHooks, never()).onVibrationCompleted(eq(vibrationId), eq(Vibration.Status.CANCELLED)); eq(Vibration.Status.CANCELLED_BY_SETTINGS_UPDATE)); verify(mManagerHooks).onVibrationThreadReleased(vibrationId); } Loading @@ -1214,10 +1220,10 @@ public class VibrationThreadTest { VibrationStepConductor conductor = startThreadAndDispatcher(vibrationId, effect); assertTrue(waitUntil(() -> mControllers.get(VIBRATOR_ID).isVibrating(), TEST_TIMEOUT_MILLIS)); conductor.notifyCancelled(/* immediate= */ false); conductor.notifyCancelled(Vibration.Status.CANCELLED_BY_USER, /* immediate= */ false); waitForCompletion(); verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED); verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED_BY_USER); // Duration extended for 10000 + 15. assertEquals(Arrays.asList(expectedOneShot(10_015)), Loading Loading @@ -1337,7 +1343,7 @@ public class VibrationThreadTest { VibrationStepConductor conductor2 = startThreadAndDispatcher(vibrationId2, effect2); // Effect2 won't complete on its own. Cancel it after a couple of repeats. Thread.sleep(150); // More than two TICKs. conductor2.notifyCancelled(/* immediate= */ false); conductor2.notifyCancelled(Vibration.Status.CANCELLED_BY_USER, /* immediate= */ false); waitForCompletion(); startThreadAndDispatcher(vibrationId3, effect3); Loading @@ -1346,7 +1352,7 @@ public class VibrationThreadTest { // Effect4 is a long oneshot, but it gets cancelled as fast as possible. long start4 = System.currentTimeMillis(); VibrationStepConductor conductor4 = startThreadAndDispatcher(vibrationId4, effect4); conductor4.notifyCancelled(/* immediate= */ true); conductor4.notifyCancelled(Vibration.Status.CANCELLED_SUPERSEDED, /* immediate= */ true); waitForCompletion(); long duration4 = System.currentTimeMillis() - start4; Loading @@ -1366,7 +1372,7 @@ public class VibrationThreadTest { // Effect2: repeating, cancelled. verify(mControllerCallbacks, atLeast(2)).onComplete(VIBRATOR_ID, vibrationId2); verifyCallbacksTriggered(vibrationId2, Vibration.Status.CANCELLED); verifyCallbacksTriggered(vibrationId2, Vibration.Status.CANCELLED_BY_USER); // The exact count of segments might vary, so just check that there's more than 2 and // all elements are the same segment. Loading @@ -1384,7 +1390,7 @@ public class VibrationThreadTest { fakeVibrator.getEffectSegments(vibrationId3)); // Effect4: cancelled quickly. verifyCallbacksTriggered(vibrationId4, Vibration.Status.CANCELLED); verifyCallbacksTriggered(vibrationId4, Vibration.Status.CANCELLED_SUPERSEDED); assertTrue("Tested duration=" + duration4, duration4 < 2000); // Effect5: normal oneshot. Don't worry about amplitude, as effect4 may or may not have Loading