Create CallbackScheduler for Vibrator HAL callbacks

This scheduler keeps a single thread looping and executing callbacks
after a delay. This mechanism is used to simulate the callbacks that
some Vibrator HALs do not support.

Bug: 153418251
Test: atest libvibratorservice_test
Change-Id: If19481dfe2eca662d33738a11257bd4509fe81ca
diff --git a/services/vibratorservice/Android.bp b/services/vibratorservice/Android.bp
index b4342bd..e3c254d 100644
--- a/services/vibratorservice/Android.bp
+++ b/services/vibratorservice/Android.bp
@@ -16,6 +16,7 @@
     name: "libvibratorservice",
 
     srcs: [
+        "VibratorCallbackScheduler.cpp",
         "VibratorHalWrapper.cpp",
     ],
 
diff --git a/services/vibratorservice/VibratorCallbackScheduler.cpp b/services/vibratorservice/VibratorCallbackScheduler.cpp
new file mode 100644
index 0000000..3f8cd67
--- /dev/null
+++ b/services/vibratorservice/VibratorCallbackScheduler.cpp
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <chrono>
+#include <thread>
+
+#include <vibratorservice/VibratorCallbackScheduler.h>
+
+namespace android {
+
+namespace vibrator {
+
+// -------------------------------------------------------------------------------------------------
+
+bool DelayedCallback::isExpired() const {
+    return mExpiration <= std::chrono::steady_clock::now();
+}
+
+DelayedCallback::Timestamp DelayedCallback::getExpiration() const {
+    return mExpiration;
+}
+
+void DelayedCallback::run() const {
+    mCallback();
+}
+
+bool DelayedCallback::operator<(const DelayedCallback& other) const {
+    return mExpiration < other.mExpiration;
+}
+
+bool DelayedCallback::operator>(const DelayedCallback& other) const {
+    return mExpiration > other.mExpiration;
+}
+
+// -------------------------------------------------------------------------------------------------
+
+CallbackScheduler::~CallbackScheduler() {
+    {
+        std::lock_guard<std::mutex> lock(mMutex);
+        mFinished = true;
+    }
+    mCondition.notify_all();
+    if (mCallbackThread && mCallbackThread->joinable()) {
+        mCallbackThread->join();
+    }
+}
+
+void CallbackScheduler::schedule(std::function<void()> callback, std::chrono::milliseconds delay) {
+    {
+        std::lock_guard<std::mutex> lock(mMutex);
+        if (mCallbackThread == nullptr) {
+            mCallbackThread = std::make_unique<std::thread>(&CallbackScheduler::loop, this);
+        }
+        mQueue.emplace(DelayedCallback(callback, delay));
+    }
+    mCondition.notify_all();
+}
+
+void CallbackScheduler::loop() {
+    while (true) {
+        std::lock_guard<std::mutex> lock(mMutex);
+        if (mFinished) {
+            // Destructor was called, so let the callback thread die.
+            break;
+        }
+        while (!mQueue.empty() && mQueue.top().isExpired()) {
+            mQueue.top().run();
+            mQueue.pop();
+        }
+        if (mQueue.empty()) {
+            // Wait until a new callback is scheduled.
+            mCondition.wait(mMutex);
+        } else {
+            // Wait until next callback expires, or a new one is scheduled.
+            mCondition.wait_until(mMutex, mQueue.top().getExpiration());
+        }
+    }
+}
+
+// -------------------------------------------------------------------------------------------------
+
+}; // namespace vibrator
+
+}; // namespace android
diff --git a/services/vibratorservice/VibratorHalWrapper.cpp b/services/vibratorservice/VibratorHalWrapper.cpp
index db27bd9..1420bf5 100644
--- a/services/vibratorservice/VibratorHalWrapper.cpp
+++ b/services/vibratorservice/VibratorHalWrapper.cpp
@@ -23,6 +23,7 @@
 
 #include <utils/Log.h>
 
+#include <vibratorservice/VibratorCallbackScheduler.h>
 #include <vibratorservice/VibratorHalWrapper.h>
 
 using android::hardware::vibrator::CompositeEffect;
@@ -46,10 +47,12 @@
 template <class T>
 HalResult<T> loadCached(const std::function<HalResult<T>()>& loadFn, std::optional<T>& cache) {
     if (cache.has_value()) {
-        return HalResult<T>::ok(cache.value());
+        // Return copy of cached value.
+        return HalResult<T>::ok(*cache);
     }
     HalResult<T> ret = loadFn();
     if (ret.isOk()) {
+        // Cache copy of returned value.
         cache.emplace(ret.value());
     }
     return ret;
@@ -62,27 +65,6 @@
     return castEffect >= *iter.begin() && castEffect <= *std::prev(iter.end());
 }
 
-template <class I, class T>
-using perform_fn = hardware::Return<void> (I::*)(T, V1_0::EffectStrength,
-                                                 V1_0::IVibrator::perform_cb);
-
-template <class I, class T>
-HalResult<milliseconds> perform(perform_fn<I, T> performFn, sp<I> handle, T effect,
-                                EffectStrength strength) {
-    V1_0::Status status;
-    int32_t lengthMs;
-    V1_0::IVibrator::perform_cb effectCallback = [&status, &lengthMs](V1_0::Status retStatus,
-                                                                      uint32_t retLengthMs) {
-        status = retStatus;
-        lengthMs = retLengthMs;
-    };
-
-    V1_0::EffectStrength effectStrength = static_cast<V1_0::EffectStrength>(strength);
-    auto result = std::invoke(performFn, handle, effect, effectStrength, effectCallback);
-
-    return HalResult<milliseconds>::fromReturn(result, status, milliseconds(lengthMs));
-}
-
 // -------------------------------------------------------------------------------------------------
 
 template <typename T>
@@ -179,7 +161,7 @@
 
 class HalCallbackWrapper : public Aidl::BnVibratorCallback {
 public:
-    HalCallbackWrapper(const std::function<void()>& completionCallback)
+    HalCallbackWrapper(std::function<void()> completionCallback)
           : mCompletionCallback(completionCallback) {}
 
     binder::Status onComplete() override {
@@ -200,8 +182,17 @@
 
 HalResult<void> AidlHalWrapper::on(milliseconds timeout,
                                    const std::function<void()>& completionCallback) {
-    auto cb = new HalCallbackWrapper(completionCallback);
-    return HalResult<void>::fromStatus(mHandle->on(timeout.count(), cb));
+    HalResult<Capabilities> capabilities = getCapabilities();
+    bool supportsCallback = capabilities.isOk() &&
+            static_cast<int32_t>(capabilities.value() & Capabilities::ON_CALLBACK);
+    auto cb = supportsCallback ? new HalCallbackWrapper(completionCallback) : nullptr;
+
+    auto ret = HalResult<void>::fromStatus(mHandle->on(timeout.count(), cb));
+    if (!supportsCallback && ret.isOk()) {
+        mCallbackScheduler->schedule(completionCallback, timeout);
+    }
+
+    return ret;
 }
 
 HalResult<void> AidlHalWrapper::off() {
@@ -227,39 +218,56 @@
 
 HalResult<Capabilities> AidlHalWrapper::getCapabilities() {
     std::lock_guard<std::mutex> lock(mCapabilitiesMutex);
-    static auto loadFn = [this]() {
-        int32_t capabilities = 0;
-        auto result = mHandle->getCapabilities(&capabilities);
-        return HalResult<Capabilities>::fromStatus(result, static_cast<Capabilities>(capabilities));
-    };
-    return loadCached<Capabilities>(loadFn, mCapabilities);
+    return loadCached<Capabilities>(std::bind(&AidlHalWrapper::getCapabilitiesInternal, this),
+                                    mCapabilities);
 }
 
 HalResult<std::vector<Effect>> AidlHalWrapper::getSupportedEffects() {
     std::lock_guard<std::mutex> lock(mSupportedEffectsMutex);
-    static auto loadFn = [this]() {
-        std::vector<Effect> supportedEffects;
-        auto result = mHandle->getSupportedEffects(&supportedEffects);
-        return HalResult<std::vector<Effect>>::fromStatus(result, supportedEffects);
-    };
-    return loadCached<std::vector<Effect>>(loadFn, mSupportedEffects);
+    return loadCached<std::vector<Effect>>(std::bind(&AidlHalWrapper::getSupportedEffectsInternal,
+                                                     this),
+                                           mSupportedEffects);
 }
 
 HalResult<milliseconds> AidlHalWrapper::performEffect(
         Effect effect, EffectStrength strength, const std::function<void()>& completionCallback) {
+    HalResult<Capabilities> capabilities = getCapabilities();
+    bool supportsCallback = capabilities.isOk() &&
+            static_cast<int32_t>(capabilities.value() & Capabilities::PERFORM_CALLBACK);
+    auto cb = supportsCallback ? new HalCallbackWrapper(completionCallback) : nullptr;
+
     int32_t lengthMs;
-    auto cb = new HalCallbackWrapper(completionCallback);
     auto result = mHandle->perform(effect, strength, cb, &lengthMs);
-    return HalResult<milliseconds>::fromStatus(result, milliseconds(lengthMs));
+    milliseconds length = milliseconds(lengthMs);
+
+    auto ret = HalResult<milliseconds>::fromStatus(result, length);
+    if (!supportsCallback && ret.isOk()) {
+        mCallbackScheduler->schedule(completionCallback, length);
+    }
+
+    return ret;
 }
 
 HalResult<void> AidlHalWrapper::performComposedEffect(
         const std::vector<CompositeEffect>& primitiveEffects,
         const std::function<void()>& completionCallback) {
+    // This method should always support callbacks, so no need to double check.
     auto cb = new HalCallbackWrapper(completionCallback);
     return HalResult<void>::fromStatus(mHandle->compose(primitiveEffects, cb));
 }
 
+HalResult<Capabilities> AidlHalWrapper::getCapabilitiesInternal() {
+    int32_t capabilities = 0;
+    auto result = mHandle->getCapabilities(&capabilities);
+    return HalResult<Capabilities>::fromStatus(result, static_cast<Capabilities>(capabilities));
+}
+
+HalResult<std::vector<Effect>> AidlHalWrapper::getSupportedEffectsInternal() {
+    std::vector<Effect> supportedEffects;
+    auto result = mHandle->getSupportedEffects(&supportedEffects);
+    return HalResult<std::vector<Effect>>::fromStatus(result, supportedEffects);
+}
+
 // -------------------------------------------------------------------------------------------------
 
 HalResult<void> HidlHalWrapperV1_0::ping() {
@@ -267,10 +275,14 @@
     return HalResult<void>::fromReturn(result);
 }
 
-HalResult<void> HidlHalWrapperV1_0::on(milliseconds timeout, const std::function<void()>&) {
+HalResult<void> HidlHalWrapperV1_0::on(milliseconds timeout,
+                                       const std::function<void()>& completionCallback) {
     auto result = mHandleV1_0->on(timeout.count());
-    auto status = result.withDefault(V1_0::Status::UNKNOWN_ERROR);
-    return HalResult<void>::fromStatus(status);
+    auto ret = HalResult<void>::fromStatus(result.withDefault(V1_0::Status::UNKNOWN_ERROR));
+    if (ret.isOk()) {
+        mCallbackScheduler->schedule(completionCallback, timeout);
+    }
+    return ret;
 }
 
 HalResult<void> HidlHalWrapperV1_0::off() {
@@ -309,11 +321,10 @@
     return HalResult<std::vector<Effect>>::unsupported();
 }
 
-HalResult<milliseconds> HidlHalWrapperV1_0::performEffect(Effect effect, EffectStrength strength,
-                                                          const std::function<void()>&) {
+HalResult<milliseconds> HidlHalWrapperV1_0::performEffect(
+        Effect effect, EffectStrength strength, const std::function<void()>& completionCallback) {
     if (isStaticCastValid<V1_0::Effect>(effect)) {
-        V1_0::Effect e = static_cast<V1_0::Effect>(effect);
-        return perform(&V1_0::IVibrator::perform, mHandleV1_0, e, strength);
+        return performInternalV1_0(effect, strength, completionCallback);
     }
 
     ALOGV("Skipped performEffect because Vibrator HAL does not support effect %s",
@@ -334,17 +345,44 @@
     return HalResult<Capabilities>::fromReturn(result, capabilities);
 }
 
+template <class I, class T>
+HalResult<milliseconds> HidlHalWrapperV1_0::performInternal(
+        perform_fn<I, T> performFn, sp<I> handle, T effect, EffectStrength strength,
+        const std::function<void()>& completionCallback) {
+    V1_0::Status status;
+    int32_t lengthMs;
+    auto effectCallback = [&status, &lengthMs](V1_0::Status retStatus, uint32_t retLengthMs) {
+        status = retStatus;
+        lengthMs = retLengthMs;
+    };
+
+    V1_0::EffectStrength effectStrength = static_cast<V1_0::EffectStrength>(strength);
+    auto result = std::invoke(performFn, handle, effect, effectStrength, effectCallback);
+    milliseconds length = milliseconds(lengthMs);
+
+    auto ret = HalResult<milliseconds>::fromReturn(result, status, length);
+    if (ret.isOk()) {
+        mCallbackScheduler->schedule(completionCallback, length);
+    }
+
+    return ret;
+}
+
+HalResult<milliseconds> HidlHalWrapperV1_0::performInternalV1_0(
+        Effect effect, EffectStrength strength, const std::function<void()>& completionCallback) {
+    V1_0::Effect e = static_cast<V1_0::Effect>(effect);
+    return performInternal(&V1_0::IVibrator::perform, mHandleV1_0, e, strength, completionCallback);
+}
+
 // -------------------------------------------------------------------------------------------------
 
-HalResult<milliseconds> HidlHalWrapperV1_1::performEffect(Effect effect, EffectStrength strength,
-                                                          const std::function<void()>&) {
+HalResult<milliseconds> HidlHalWrapperV1_1::performEffect(
+        Effect effect, EffectStrength strength, const std::function<void()>& completionCallback) {
     if (isStaticCastValid<V1_0::Effect>(effect)) {
-        V1_0::Effect e = static_cast<V1_0::Effect>(effect);
-        return perform(&V1_0::IVibrator::perform, mHandleV1_0, e, strength);
+        return performInternalV1_0(effect, strength, completionCallback);
     }
     if (isStaticCastValid<V1_1::Effect_1_1>(effect)) {
-        V1_1::Effect_1_1 e = static_cast<V1_1::Effect_1_1>(effect);
-        return perform(&V1_1::IVibrator::perform_1_1, mHandleV1_1, e, strength);
+        return performInternalV1_1(effect, strength, completionCallback);
     }
 
     ALOGV("Skipped performEffect because Vibrator HAL does not support effect %s",
@@ -352,21 +390,25 @@
     return HalResult<milliseconds>::unsupported();
 }
 
+HalResult<milliseconds> HidlHalWrapperV1_1::performInternalV1_1(
+        Effect effect, EffectStrength strength, const std::function<void()>& completionCallback) {
+    V1_1::Effect_1_1 e = static_cast<V1_1::Effect_1_1>(effect);
+    return performInternal(&V1_1::IVibrator::perform_1_1, mHandleV1_1, e, strength,
+                           completionCallback);
+}
+
 // -------------------------------------------------------------------------------------------------
 
-HalResult<milliseconds> HidlHalWrapperV1_2::performEffect(Effect effect, EffectStrength strength,
-                                                          const std::function<void()>&) {
+HalResult<milliseconds> HidlHalWrapperV1_2::performEffect(
+        Effect effect, EffectStrength strength, const std::function<void()>& completionCallback) {
     if (isStaticCastValid<V1_0::Effect>(effect)) {
-        V1_0::Effect e = static_cast<V1_0::Effect>(effect);
-        return perform(&V1_0::IVibrator::perform, mHandleV1_0, e, strength);
+        return performInternalV1_0(effect, strength, completionCallback);
     }
     if (isStaticCastValid<V1_1::Effect_1_1>(effect)) {
-        V1_1::Effect_1_1 e = static_cast<V1_1::Effect_1_1>(effect);
-        return perform(&V1_1::IVibrator::perform_1_1, mHandleV1_1, e, strength);
+        return performInternalV1_1(effect, strength, completionCallback);
     }
     if (isStaticCastValid<V1_2::Effect>(effect)) {
-        V1_2::Effect e = static_cast<V1_2::Effect>(effect);
-        return perform(&V1_2::IVibrator::perform_1_2, mHandleV1_2, e, strength);
+        return performInternalV1_2(effect, strength, completionCallback);
     }
 
     ALOGV("Skipped performEffect because Vibrator HAL does not support effect %s",
@@ -374,6 +416,13 @@
     return HalResult<milliseconds>::unsupported();
 }
 
+HalResult<milliseconds> HidlHalWrapperV1_2::performInternalV1_2(
+        Effect effect, EffectStrength strength, const std::function<void()>& completionCallback) {
+    V1_2::Effect e = static_cast<V1_2::Effect>(effect);
+    return performInternal(&V1_2::IVibrator::perform_1_2, mHandleV1_2, e, strength,
+                           completionCallback);
+}
+
 // -------------------------------------------------------------------------------------------------
 
 HalResult<void> HidlHalWrapperV1_3::setExternalControl(bool enabled) {
@@ -381,23 +430,19 @@
     return HalResult<void>::fromStatus(result.withDefault(V1_0::Status::UNKNOWN_ERROR));
 }
 
-HalResult<milliseconds> HidlHalWrapperV1_3::performEffect(Effect effect, EffectStrength strength,
-                                                          const std::function<void()>&) {
+HalResult<milliseconds> HidlHalWrapperV1_3::performEffect(
+        Effect effect, EffectStrength strength, const std::function<void()>& completionCallback) {
     if (isStaticCastValid<V1_0::Effect>(effect)) {
-        V1_0::Effect e = static_cast<V1_0::Effect>(effect);
-        return perform(&V1_0::IVibrator::perform, mHandleV1_0, e, strength);
+        return performInternalV1_0(effect, strength, completionCallback);
     }
     if (isStaticCastValid<V1_1::Effect_1_1>(effect)) {
-        V1_1::Effect_1_1 e = static_cast<V1_1::Effect_1_1>(effect);
-        return perform(&V1_1::IVibrator::perform_1_1, mHandleV1_1, e, strength);
+        return performInternalV1_1(effect, strength, completionCallback);
     }
     if (isStaticCastValid<V1_2::Effect>(effect)) {
-        V1_2::Effect e = static_cast<V1_2::Effect>(effect);
-        return perform(&V1_2::IVibrator::perform_1_2, mHandleV1_2, e, strength);
+        return performInternalV1_2(effect, strength, completionCallback);
     }
     if (isStaticCastValid<V1_3::Effect>(effect)) {
-        V1_3::Effect e = static_cast<V1_3::Effect>(effect);
-        return perform(&V1_3::IVibrator::perform_1_3, mHandleV1_3, e, strength);
+        return performInternalV1_3(effect, strength, completionCallback);
     }
 
     ALOGV("Skipped performEffect because Vibrator HAL does not support effect %s",
@@ -418,6 +463,13 @@
     return HalResult<Capabilities>::fromReturn(result, capabilities);
 }
 
+HalResult<milliseconds> HidlHalWrapperV1_3::performInternalV1_3(
+        Effect effect, EffectStrength strength, const std::function<void()>& completionCallback) {
+    V1_3::Effect e = static_cast<V1_3::Effect>(effect);
+    return performInternal(&V1_3::IVibrator::perform_1_3, mHandleV1_3, e, strength,
+                           completionCallback);
+}
+
 // -------------------------------------------------------------------------------------------------
 
 }; // namespace vibrator
diff --git a/services/vibratorservice/include/vibratorservice/VibratorCallbackScheduler.h b/services/vibratorservice/include/vibratorservice/VibratorCallbackScheduler.h
new file mode 100644
index 0000000..2c194b5
--- /dev/null
+++ b/services/vibratorservice/include/vibratorservice/VibratorCallbackScheduler.h
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_VIBRATOR_CALLBACK_SCHEDULER_H
+#define ANDROID_VIBRATOR_CALLBACK_SCHEDULER_H
+
+#include <android-base/thread_annotations.h>
+#include <chrono>
+#include <condition_variable>
+#include <queue>
+#include <thread>
+
+namespace android {
+
+namespace vibrator {
+
+// Wrapper for a callback to be executed after a delay.
+class DelayedCallback {
+public:
+    using Timestamp = std::chrono::time_point<std::chrono::steady_clock>;
+
+    DelayedCallback(std::function<void()> callback, std::chrono::milliseconds delay)
+          : mCallback(callback), mExpiration(std::chrono::steady_clock::now() + delay) {}
+    ~DelayedCallback() = default;
+
+    void run() const;
+    bool isExpired() const;
+    Timestamp getExpiration() const;
+
+    // Compare by expiration time, where A < B when A expires first.
+    bool operator<(const DelayedCallback& other) const;
+    bool operator>(const DelayedCallback& other) const;
+
+private:
+    std::function<void()> mCallback;
+    Timestamp mExpiration;
+};
+
+// Schedules callbacks to be executed after a delay.
+class CallbackScheduler {
+public:
+    CallbackScheduler() : mCallbackThread(nullptr), mFinished(false) {}
+    virtual ~CallbackScheduler();
+
+    virtual void schedule(std::function<void()> callback, std::chrono::milliseconds delay);
+
+private:
+    std::condition_variable_any mCondition;
+    std::mutex mMutex;
+
+    // Lazily instantiated only at the first time this scheduler is used.
+    std::unique_ptr<std::thread> mCallbackThread;
+
+    // Used to quit the callback thread when this instance is being destroyed.
+    bool mFinished GUARDED_BY(mMutex);
+
+    // Priority queue with reverse comparator, so tasks that expire first will be on top.
+    std::priority_queue<DelayedCallback, std::vector<DelayedCallback>,
+                        std::greater<DelayedCallback>>
+            mQueue GUARDED_BY(mMutex);
+
+    void loop();
+};
+
+}; // namespace vibrator
+
+}; // namespace android
+
+#endif // ANDROID_VIBRATOR_CALLBACK_SCHEDULER_H
diff --git a/services/vibratorservice/include/vibratorservice/VibratorHalWrapper.h b/services/vibratorservice/include/vibratorservice/VibratorHalWrapper.h
index 1a1f64b..0f9aacb 100644
--- a/services/vibratorservice/include/vibratorservice/VibratorHalWrapper.h
+++ b/services/vibratorservice/include/vibratorservice/VibratorHalWrapper.h
@@ -21,6 +21,8 @@
 #include <android/hardware/vibrator/1.3/IVibrator.h>
 #include <android/hardware/vibrator/IVibrator.h>
 
+#include <vibratorservice/VibratorCallbackScheduler.h>
+
 namespace android {
 
 namespace vibrator {
@@ -46,7 +48,7 @@
                                    hardware::vibrator::V1_0::Status status, T data);
 
     // This will throw std::bad_optional_access if this result is not ok.
-    T value() const { return mValue.value(); }
+    const T& value() const { return mValue.value(); }
     bool isOk() const { return !mUnsupported && mValue.has_value(); }
     bool isFailed() const { return !mUnsupported && !mValue.has_value(); }
     bool isUnsupported() const { return mUnsupported; }
@@ -122,6 +124,8 @@
 // Wrapper for Vibrator HAL handlers.
 class HalWrapper {
 public:
+    explicit HalWrapper(std::shared_ptr<CallbackScheduler> scheduler)
+          : mCallbackScheduler(std::move(scheduler)) {}
     virtual ~HalWrapper() = default;
 
     virtual HalResult<void> ping() = 0;
@@ -147,13 +151,19 @@
     virtual HalResult<void> performComposedEffect(
             const std::vector<hardware::vibrator::CompositeEffect>& primitiveEffects,
             const std::function<void()>& completionCallback) = 0;
+
+protected:
+    // Shared pointer to allow CallbackScheduler to outlive this wrapper.
+    const std::shared_ptr<CallbackScheduler> mCallbackScheduler;
 };
 
 // Wrapper for the AIDL Vibrator HAL.
 class AidlHalWrapper : public HalWrapper {
 public:
-    explicit AidlHalWrapper(sp<hardware::vibrator::IVibrator> handle)
-          : mHandle(std::move(handle)) {}
+    AidlHalWrapper(std::shared_ptr<CallbackScheduler> scheduler,
+                   sp<hardware::vibrator::IVibrator> handle)
+          : HalWrapper(std::move(scheduler)), mHandle(std::move(handle)) {}
+    virtual ~AidlHalWrapper() = default;
 
     virtual HalResult<void> ping() override;
 
@@ -186,13 +196,19 @@
     std::optional<Capabilities> mCapabilities GUARDED_BY(mCapabilitiesMutex);
     std::optional<std::vector<hardware::vibrator::Effect>> mSupportedEffects
             GUARDED_BY(mSupportedEffectsMutex);
+
+    // Loads directly from IVibrator handle, skipping caches.
+    HalResult<Capabilities> getCapabilitiesInternal();
+    HalResult<std::vector<hardware::vibrator::Effect>> getSupportedEffectsInternal();
 };
 
 // Wrapper for the HDIL Vibrator HAL v1.0.
 class HidlHalWrapperV1_0 : public HalWrapper {
 public:
-    explicit HidlHalWrapperV1_0(sp<hardware::vibrator::V1_0::IVibrator> handle)
-          : mHandleV1_0(std::move(handle)) {}
+    HidlHalWrapperV1_0(std::shared_ptr<CallbackScheduler> scheduler,
+                       sp<hardware::vibrator::V1_0::IVibrator> handle)
+          : HalWrapper(std::move(scheduler)), mHandleV1_0(std::move(handle)) {}
+    virtual ~HidlHalWrapperV1_0() = default;
 
     virtual HalResult<void> ping() override;
 
@@ -225,14 +241,31 @@
 
     // Loads directly from IVibrator handle, skipping the mCapabilities cache.
     virtual HalResult<Capabilities> getCapabilitiesInternal();
+
+    template <class I, class T>
+    using perform_fn =
+            hardware::Return<void> (I::*)(T, hardware::vibrator::V1_0::EffectStrength,
+                                          hardware::vibrator::V1_0::IVibrator::perform_cb);
+
+    template <class I, class T>
+    HalResult<std::chrono::milliseconds> performInternal(
+            perform_fn<I, T> performFn, sp<I> handle, T effect,
+            hardware::vibrator::EffectStrength strength,
+            const std::function<void()>& completionCallback);
+
+    HalResult<std::chrono::milliseconds> performInternalV1_0(
+            hardware::vibrator::Effect effect, hardware::vibrator::EffectStrength strength,
+            const std::function<void()>& completionCallback);
 };
 
 // Wrapper for the HDIL Vibrator HAL v1.1.
 class HidlHalWrapperV1_1 : public HidlHalWrapperV1_0 {
 public:
-    explicit HidlHalWrapperV1_1(sp<hardware::vibrator::V1_0::IVibrator> handleV1_0)
-          : HidlHalWrapperV1_0(handleV1_0),
+    HidlHalWrapperV1_1(std::shared_ptr<CallbackScheduler> scheduler,
+                       sp<hardware::vibrator::V1_0::IVibrator> handleV1_0)
+          : HidlHalWrapperV1_0(std::move(scheduler), handleV1_0),
             mHandleV1_1(hardware::vibrator::V1_1::IVibrator::castFrom(handleV1_0)) {}
+    virtual ~HidlHalWrapperV1_1() = default;
 
     virtual HalResult<std::chrono::milliseconds> performEffect(
             hardware::vibrator::Effect effect, hardware::vibrator::EffectStrength strength,
@@ -240,14 +273,20 @@
 
 protected:
     const sp<hardware::vibrator::V1_1::IVibrator> mHandleV1_1;
+
+    HalResult<std::chrono::milliseconds> performInternalV1_1(
+            hardware::vibrator::Effect effect, hardware::vibrator::EffectStrength strength,
+            const std::function<void()>& completionCallback);
 };
 
 // Wrapper for the HDIL Vibrator HAL v1.2.
 class HidlHalWrapperV1_2 : public HidlHalWrapperV1_1 {
 public:
-    explicit HidlHalWrapperV1_2(sp<hardware::vibrator::V1_0::IVibrator> handleV1_0)
-          : HidlHalWrapperV1_1(handleV1_0),
+    HidlHalWrapperV1_2(std::shared_ptr<CallbackScheduler> scheduler,
+                       sp<hardware::vibrator::V1_0::IVibrator> handleV1_0)
+          : HidlHalWrapperV1_1(std::move(scheduler), handleV1_0),
             mHandleV1_2(hardware::vibrator::V1_2::IVibrator::castFrom(handleV1_0)) {}
+    virtual ~HidlHalWrapperV1_2() = default;
 
     virtual HalResult<std::chrono::milliseconds> performEffect(
             hardware::vibrator::Effect effect, hardware::vibrator::EffectStrength strength,
@@ -255,14 +294,20 @@
 
 protected:
     const sp<hardware::vibrator::V1_2::IVibrator> mHandleV1_2;
+
+    HalResult<std::chrono::milliseconds> performInternalV1_2(
+            hardware::vibrator::Effect effect, hardware::vibrator::EffectStrength strength,
+            const std::function<void()>& completionCallback);
 };
 
 // Wrapper for the HDIL Vibrator HAL v1.3.
 class HidlHalWrapperV1_3 : public HidlHalWrapperV1_2 {
 public:
-    explicit HidlHalWrapperV1_3(sp<hardware::vibrator::V1_0::IVibrator> handleV1_0)
-          : HidlHalWrapperV1_2(handleV1_0),
+    HidlHalWrapperV1_3(std::shared_ptr<CallbackScheduler> scheduler,
+                       sp<hardware::vibrator::V1_0::IVibrator> handleV1_0)
+          : HidlHalWrapperV1_2(std::move(scheduler), handleV1_0),
             mHandleV1_3(hardware::vibrator::V1_3::IVibrator::castFrom(handleV1_0)) {}
+    virtual ~HidlHalWrapperV1_3() = default;
 
     virtual HalResult<void> setExternalControl(bool enabled) override;
 
@@ -274,6 +319,9 @@
     const sp<hardware::vibrator::V1_3::IVibrator> mHandleV1_3;
 
     virtual HalResult<Capabilities> getCapabilitiesInternal() override;
+    HalResult<std::chrono::milliseconds> performInternalV1_3(
+            hardware::vibrator::Effect effect, hardware::vibrator::EffectStrength strength,
+            const std::function<void()>& completionCallback);
 };
 
 // -------------------------------------------------------------------------------------------------
diff --git a/services/vibratorservice/test/Android.bp b/services/vibratorservice/test/Android.bp
index 7c038e9..fa399ad 100644
--- a/services/vibratorservice/test/Android.bp
+++ b/services/vibratorservice/test/Android.bp
@@ -16,6 +16,7 @@
     name: "libvibratorservice_test",
     test_suites: ["device-tests"],
     srcs: [
+        "VibratorCallbackSchedulerTest.cpp",
         "VibratorHalWrapperAidlTest.cpp",
         "VibratorHalWrapperHidlV1_0Test.cpp",
         "VibratorHalWrapperHidlV1_1Test.cpp",
diff --git a/services/vibratorservice/test/VibratorCallbackSchedulerTest.cpp b/services/vibratorservice/test/VibratorCallbackSchedulerTest.cpp
new file mode 100644
index 0000000..aaeb8f9
--- /dev/null
+++ b/services/vibratorservice/test/VibratorCallbackSchedulerTest.cpp
@@ -0,0 +1,124 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *            http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "VibratorHalWrapperAidlTest"
+
+#include <android-base/thread_annotations.h>
+#include <android/hardware/vibrator/IVibrator.h>
+#include <condition_variable>
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+#include <utils/Log.h>
+#include <thread>
+
+#include <vibratorservice/VibratorCallbackScheduler.h>
+
+using std::chrono::milliseconds;
+using std::chrono::steady_clock;
+using std::chrono::time_point;
+
+using namespace android;
+using namespace std::chrono_literals;
+using namespace testing;
+
+// -------------------------------------------------------------------------------------------------
+
+class VibratorCallbackSchedulerTest : public Test {
+public:
+    void SetUp() override {
+        mScheduler = std::make_unique<vibrator::CallbackScheduler>();
+        std::lock_guard<std::mutex> lock(mMutex);
+        mExpiredCallbacks.clear();
+    }
+
+protected:
+    std::mutex mMutex;
+    std::condition_variable_any mCondition;
+    std::unique_ptr<vibrator::CallbackScheduler> mScheduler = nullptr;
+    std::vector<int32_t> mExpiredCallbacks GUARDED_BY(mMutex);
+
+    std::function<void()> createCallback(int32_t id) {
+        return [=]() {
+            {
+                std::lock_guard<std::mutex> lock(mMutex);
+                mExpiredCallbacks.push_back(id);
+            }
+            mCondition.notify_all();
+        };
+    }
+
+    std::vector<int32_t> getExpiredCallbacks() {
+        std::lock_guard<std::mutex> lock(mMutex);
+        return std::vector<int32_t>(mExpiredCallbacks);
+    }
+
+    bool waitForCallbacks(uint32_t callbackCount, milliseconds timeout) {
+        time_point<steady_clock> expiration = steady_clock::now() + timeout;
+        while (steady_clock::now() < expiration) {
+            std::lock_guard<std::mutex> lock(mMutex);
+            if (callbackCount <= mExpiredCallbacks.size()) {
+                return true;
+            }
+            mCondition.wait_until(mMutex, expiration);
+        }
+        return false;
+    }
+};
+
+// -------------------------------------------------------------------------------------------------
+
+TEST_F(VibratorCallbackSchedulerTest, TestScheduleRunsOnlyAfterDelay) {
+    mScheduler->schedule(createCallback(1), 15ms);
+
+    // Not triggered before delay.
+    ASSERT_FALSE(waitForCallbacks(1, 10ms));
+    ASSERT_TRUE(getExpiredCallbacks().empty());
+
+    ASSERT_TRUE(waitForCallbacks(1, 10ms));
+    ASSERT_THAT(getExpiredCallbacks(), ElementsAre(1));
+}
+
+TEST_F(VibratorCallbackSchedulerTest, TestScheduleMultipleCallbacksRunsInDelayOrder) {
+    mScheduler->schedule(createCallback(1), 10ms);
+    mScheduler->schedule(createCallback(2), 5ms);
+    mScheduler->schedule(createCallback(3), 1ms);
+
+    ASSERT_TRUE(waitForCallbacks(3, 15ms));
+    ASSERT_THAT(getExpiredCallbacks(), ElementsAre(3, 2, 1));
+}
+
+TEST_F(VibratorCallbackSchedulerTest, TestScheduleInParallelRunsInDelayOrder) {
+    std::vector<std::thread> threads;
+    for (int i = 0; i < 5; i++) {
+        threads.push_back(std::thread(
+                [=]() { mScheduler->schedule(createCallback(i), milliseconds(10 + 2 * i)); }));
+    }
+    std::for_each(threads.begin(), threads.end(), [](std::thread& t) { t.join(); });
+
+    ASSERT_TRUE(waitForCallbacks(5, 25ms));
+    ASSERT_THAT(getExpiredCallbacks(), ElementsAre(0, 1, 2, 3, 4));
+}
+
+TEST_F(VibratorCallbackSchedulerTest, TestDestructorDropsPendingCallbacksAndKillsThread) {
+    mScheduler->schedule(createCallback(1), 5ms);
+    mScheduler.reset(nullptr);
+
+    // Should time out waiting for callback to run.
+    ASSERT_FALSE(waitForCallbacks(1, 10ms));
+    ASSERT_TRUE(getExpiredCallbacks().empty());
+}
diff --git a/services/vibratorservice/test/VibratorHalWrapperAidlTest.cpp b/services/vibratorservice/test/VibratorHalWrapperAidlTest.cpp
index 6db449a..0f2d7bc 100644
--- a/services/vibratorservice/test/VibratorHalWrapperAidlTest.cpp
+++ b/services/vibratorservice/test/VibratorHalWrapperAidlTest.cpp
@@ -24,6 +24,7 @@
 #include <utils/Log.h>
 #include <thread>
 
+#include <vibratorservice/VibratorCallbackScheduler.h>
 #include <vibratorservice/VibratorHalWrapper.h>
 
 #include "test_utils.h"
@@ -88,11 +89,13 @@
     void SetUp() override {
         mMockBinder = new StrictMock<MockBinder>();
         mMockHal = new StrictMock<MockIVibrator>();
-        mWrapper = std::make_unique<vibrator::AidlHalWrapper>(mMockHal);
+        mMockScheduler = std::make_shared<StrictMock<vibrator::MockCallbackScheduler>>();
+        mWrapper = std::make_unique<vibrator::AidlHalWrapper>(mMockScheduler, mMockHal);
         ASSERT_NE(mWrapper, nullptr);
     }
 
 protected:
+    std::shared_ptr<StrictMock<vibrator::MockCallbackScheduler>> mMockScheduler = nullptr;
     std::unique_ptr<vibrator::HalWrapper> mWrapper = nullptr;
     sp<StrictMock<MockIVibrator>> mMockHal = nullptr;
     sp<StrictMock<MockBinder>> mMockBinder = nullptr;
@@ -124,9 +127,13 @@
     ASSERT_TRUE(mWrapper->ping().isFailed());
 }
 
-TEST_F(VibratorHalWrapperAidlTest, TestOn) {
+TEST_F(VibratorHalWrapperAidlTest, TestOnWithCallbackSupport) {
     {
         InSequence seq;
+        EXPECT_CALL(*mMockHal.get(), getCapabilities(_))
+                .Times(Exactly(1))
+                .WillRepeatedly(
+                        DoAll(SetArgPointee<0>(IVibrator::CAP_ON_CALLBACK), Return(Status())));
         EXPECT_CALL(*mMockHal.get(), on(Eq(10), _))
                 .Times(Exactly(1))
                 .WillRepeatedly(DoAll(TriggerCallbackInArg1(), Return(Status())));
@@ -146,11 +153,46 @@
     ASSERT_EQ(1, *callbackCounter.get());
 
     ASSERT_TRUE(mWrapper->on(100ms, callback).isUnsupported());
-    // Callback not triggered
+    // Callback not triggered for unsupported
     ASSERT_EQ(1, *callbackCounter.get());
 
     ASSERT_TRUE(mWrapper->on(1000ms, callback).isFailed());
-    // Callback not triggered
+    // Callback not triggered on failure
+    ASSERT_EQ(1, *callbackCounter.get());
+}
+
+TEST_F(VibratorHalWrapperAidlTest, TestOnWithoutCallbackSupport) {
+    {
+        InSequence seq;
+        EXPECT_CALL(*mMockHal.get(), getCapabilities(_))
+                .Times(Exactly(1))
+                .WillRepeatedly(
+                        DoAll(SetArgPointee<0>(IVibrator::CAP_COMPOSE_EFFECTS), Return(Status())));
+        EXPECT_CALL(*mMockHal.get(), on(Eq(10), _))
+                .Times(Exactly(1))
+                .WillRepeatedly(Return(Status()));
+        EXPECT_CALL(*mMockScheduler.get(), schedule(_, Eq(10ms)))
+                .Times(Exactly(1))
+                .WillRepeatedly(vibrator::TriggerSchedulerCallback());
+        EXPECT_CALL(*mMockHal.get(), on(Eq(11), _))
+                .Times(Exactly(1))
+                .WillRepeatedly(Return(
+                        Status::fromExceptionCode(Status::Exception::EX_UNSUPPORTED_OPERATION)));
+        EXPECT_CALL(*mMockHal.get(), on(Eq(12), _))
+                .Times(Exactly(1))
+                .WillRepeatedly(Return(Status::fromExceptionCode(Status::Exception::EX_SECURITY)));
+    }
+
+    std::unique_ptr<int32_t> callbackCounter = std::make_unique<int32_t>();
+    auto callback = vibrator::TestFactory::createCountingCallback(callbackCounter.get());
+
+    ASSERT_TRUE(mWrapper->on(10ms, callback).isOk());
+    ASSERT_EQ(1, *callbackCounter.get());
+
+    ASSERT_TRUE(mWrapper->on(11ms, callback).isUnsupported());
+    ASSERT_TRUE(mWrapper->on(12ms, callback).isFailed());
+
+    // Callback not triggered for unsupported and on failure
     ASSERT_EQ(1, *callbackCounter.get());
 }
 
@@ -274,6 +316,10 @@
         }));
     }
     std::for_each(threads.begin(), threads.end(), [](std::thread& t) { t.join(); });
+
+    auto result = mWrapper->getCapabilities();
+    ASSERT_TRUE(result.isOk());
+    ASSERT_EQ(vibrator::Capabilities::ON_CALLBACK, result.value());
 }
 
 TEST_F(VibratorHalWrapperAidlTest, TestGetSupportedEffectsDoesNotCacheFailedResult) {
@@ -314,11 +360,19 @@
         }));
     }
     std::for_each(threads.begin(), threads.end(), [](std::thread& t) { t.join(); });
+
+    auto result = mWrapper->getSupportedEffects();
+    ASSERT_TRUE(result.isOk());
+    ASSERT_EQ(supportedEffects, result.value());
 }
 
-TEST_F(VibratorHalWrapperAidlTest, TestPerformEffect) {
+TEST_F(VibratorHalWrapperAidlTest, TestPerformEffectWithCallbackSupport) {
     {
         InSequence seq;
+        EXPECT_CALL(*mMockHal.get(), getCapabilities(_))
+                .Times(Exactly(1))
+                .WillRepeatedly(
+                        DoAll(SetArgPointee<0>(IVibrator::CAP_PERFORM_CALLBACK), Return(Status())));
         EXPECT_CALL(*mMockHal.get(), perform(Eq(Effect::CLICK), Eq(EffectStrength::LIGHT), _, _))
                 .Times(Exactly(1))
                 .WillRepeatedly(
@@ -342,12 +396,52 @@
 
     result = mWrapper->performEffect(Effect::POP, EffectStrength::MEDIUM, callback);
     ASSERT_TRUE(result.isUnsupported());
-    // Callback not triggered
+    // Callback not triggered for unsupported
     ASSERT_EQ(1, *callbackCounter.get());
 
     result = mWrapper->performEffect(Effect::THUD, EffectStrength::STRONG, callback);
     ASSERT_TRUE(result.isFailed());
-    // Callback not triggered
+    // Callback not triggered on failure
+    ASSERT_EQ(1, *callbackCounter.get());
+}
+
+TEST_F(VibratorHalWrapperAidlTest, TestPerformEffectWithoutCallbackSupport) {
+    {
+        InSequence seq;
+        EXPECT_CALL(*mMockHal.get(), getCapabilities(_))
+                .Times(Exactly(1))
+                .WillRepeatedly(
+                        DoAll(SetArgPointee<0>(IVibrator::CAP_ON_CALLBACK), Return(Status())));
+        EXPECT_CALL(*mMockHal.get(), perform(Eq(Effect::CLICK), Eq(EffectStrength::LIGHT), _, _))
+                .Times(Exactly(1))
+                .WillRepeatedly(DoAll(SetArgPointee<3>(10), Return(Status())));
+        EXPECT_CALL(*mMockScheduler.get(), schedule(_, Eq(10ms)))
+                .Times(Exactly(1))
+                .WillRepeatedly(vibrator::TriggerSchedulerCallback());
+        EXPECT_CALL(*mMockHal.get(), perform(Eq(Effect::POP), Eq(EffectStrength::MEDIUM), _, _))
+                .Times(Exactly(1))
+                .WillRepeatedly(Return(
+                        Status::fromExceptionCode(Status::Exception::EX_UNSUPPORTED_OPERATION)));
+        EXPECT_CALL(*mMockHal.get(), perform(Eq(Effect::THUD), Eq(EffectStrength::STRONG), _, _))
+                .Times(Exactly(1))
+                .WillRepeatedly(Return(Status::fromExceptionCode(Status::Exception::EX_SECURITY)));
+    }
+
+    std::unique_ptr<int32_t> callbackCounter = std::make_unique<int32_t>();
+    auto callback = vibrator::TestFactory::createCountingCallback(callbackCounter.get());
+
+    auto result = mWrapper->performEffect(Effect::CLICK, EffectStrength::LIGHT, callback);
+    ASSERT_TRUE(result.isOk());
+    ASSERT_EQ(10ms, result.value());
+    ASSERT_EQ(1, *callbackCounter.get());
+
+    result = mWrapper->performEffect(Effect::POP, EffectStrength::MEDIUM, callback);
+    ASSERT_TRUE(result.isUnsupported());
+
+    result = mWrapper->performEffect(Effect::THUD, EffectStrength::STRONG, callback);
+    ASSERT_TRUE(result.isFailed());
+
+    // Callback not triggered for unsupported and on failure
     ASSERT_EQ(1, *callbackCounter.get());
 }
 
@@ -383,11 +477,11 @@
 
     result = mWrapper->performComposedEffect(singleEffect, callback);
     ASSERT_TRUE(result.isUnsupported());
-    // Callback not triggered
+    // Callback not triggered for unsupported
     ASSERT_EQ(1, *callbackCounter.get());
 
     result = mWrapper->performComposedEffect(multipleEffects, callback);
     ASSERT_TRUE(result.isFailed());
-    // Callback not triggered
+    // Callback not triggered on failure
     ASSERT_EQ(1, *callbackCounter.get());
 }
diff --git a/services/vibratorservice/test/VibratorHalWrapperHidlV1_0Test.cpp b/services/vibratorservice/test/VibratorHalWrapperHidlV1_0Test.cpp
index 7f1016f..7eb4059 100644
--- a/services/vibratorservice/test/VibratorHalWrapperHidlV1_0Test.cpp
+++ b/services/vibratorservice/test/VibratorHalWrapperHidlV1_0Test.cpp
@@ -24,6 +24,7 @@
 #include <utils/Log.h>
 #include <thread>
 
+#include <vibratorservice/VibratorCallbackScheduler.h>
 #include <vibratorservice/VibratorHalWrapper.h>
 
 #include "test_utils.h"
@@ -59,11 +60,13 @@
 public:
     void SetUp() override {
         mMockHal = new StrictMock<MockIVibratorV1_0>();
-        mWrapper = std::make_unique<vibrator::HidlHalWrapperV1_0>(mMockHal);
+        mMockScheduler = std::make_shared<StrictMock<vibrator::MockCallbackScheduler>>();
+        mWrapper = std::make_unique<vibrator::HidlHalWrapperV1_0>(mMockScheduler, mMockHal);
         ASSERT_NE(mWrapper, nullptr);
     }
 
 protected:
+    std::shared_ptr<StrictMock<vibrator::MockCallbackScheduler>> mMockScheduler = nullptr;
     std::unique_ptr<vibrator::HalWrapper> mWrapper = nullptr;
     sp<StrictMock<MockIVibratorV1_0>> mMockHal = nullptr;
 };
@@ -89,17 +92,20 @@
                 .Times(Exactly(1))
                 .WillRepeatedly(
                         [](uint32_t) { return hardware::Return<V1_0::Status>(V1_0::Status::OK); });
+        EXPECT_CALL(*mMockScheduler.get(), schedule(_, Eq(1ms)))
+                .Times(Exactly(1))
+                .WillRepeatedly(vibrator::TriggerSchedulerCallback());
         EXPECT_CALL(*mMockHal.get(), on(Eq(static_cast<uint32_t>(10))))
                 .Times(Exactly(1))
                 .WillRepeatedly([](uint32_t) {
                     return hardware::Return<V1_0::Status>(V1_0::Status::UNSUPPORTED_OPERATION);
                 });
-        EXPECT_CALL(*mMockHal.get(), on(Eq(static_cast<uint32_t>(100))))
+        EXPECT_CALL(*mMockHal.get(), on(Eq(static_cast<uint32_t>(11))))
                 .Times(Exactly(1))
                 .WillRepeatedly([](uint32_t) {
                     return hardware::Return<V1_0::Status>(V1_0::Status::BAD_VALUE);
                 });
-        EXPECT_CALL(*mMockHal.get(), on(Eq(static_cast<uint32_t>(1000))))
+        EXPECT_CALL(*mMockHal.get(), on(Eq(static_cast<uint32_t>(12))))
                 .Times(Exactly(1))
                 .WillRepeatedly([](uint32_t) {
                     return hardware::Return<V1_0::Status>(hardware::Status::fromExceptionCode(-1));
@@ -110,20 +116,14 @@
     auto callback = vibrator::TestFactory::createCountingCallback(callbackCounter.get());
 
     ASSERT_TRUE(mWrapper->on(1ms, callback).isOk());
-    // TODO(b/153418251): check callback will be triggered once implemented
-    ASSERT_EQ(0, *callbackCounter.get());
+    ASSERT_EQ(1, *callbackCounter.get());
 
     ASSERT_TRUE(mWrapper->on(10ms, callback).isUnsupported());
-    // Callback not triggered
-    ASSERT_EQ(0, *callbackCounter.get());
+    ASSERT_TRUE(mWrapper->on(11ms, callback).isFailed());
+    ASSERT_TRUE(mWrapper->on(12ms, callback).isFailed());
 
-    ASSERT_TRUE(mWrapper->on(100ms, callback).isFailed());
-    // Callback not triggered
-    ASSERT_EQ(0, *callbackCounter.get());
-
-    ASSERT_TRUE(mWrapper->on(1000ms, callback).isFailed());
-    // Callback not triggered
-    ASSERT_EQ(0, *callbackCounter.get());
+    // Callback not triggered for unsupported and on failure
+    ASSERT_EQ(1, *callbackCounter.get());
 }
 
 TEST_F(VibratorHalWrapperHidlV1_0Test, TestOff) {
@@ -226,6 +226,10 @@
         }));
     }
     std::for_each(threads.begin(), threads.end(), [](std::thread& t) { t.join(); });
+
+    auto result = mWrapper->getCapabilities();
+    ASSERT_TRUE(result.isOk());
+    ASSERT_EQ(vibrator::Capabilities::AMPLITUDE_CONTROL, result.value());
 }
 
 TEST_F(VibratorHalWrapperHidlV1_0Test, TestGetSupportedEffectsUnsupported) {
@@ -240,9 +244,12 @@
                 .Times(Exactly(1))
                 .WillRepeatedly(
                         [](V1_0::Effect, V1_0::EffectStrength, MockIVibratorV1_0::perform_cb cb) {
-                            cb(V1_0::Status::OK, 100);
+                            cb(V1_0::Status::OK, 10);
                             return hardware::Return<void>();
                         });
+        EXPECT_CALL(*mMockScheduler.get(), schedule(_, Eq(10ms)))
+                .Times(Exactly(1))
+                .WillRepeatedly(vibrator::TriggerSchedulerCallback());
         EXPECT_CALL(*mMockHal.get(),
                     perform(Eq(V1_0::Effect::CLICK), Eq(V1_0::EffectStrength::MEDIUM), _))
                 .Times(Exactly(1))
@@ -269,24 +276,20 @@
 
     auto result = mWrapper->performEffect(Effect::CLICK, EffectStrength::LIGHT, callback);
     ASSERT_TRUE(result.isOk());
-    ASSERT_EQ(100ms, result.value());
-    // TODO(b/153418251): check callback will be triggered once implemented
-    ASSERT_EQ(0, *callbackCounter.get());
+    ASSERT_EQ(10ms, result.value());
+    ASSERT_EQ(1, *callbackCounter.get());
 
     result = mWrapper->performEffect(Effect::CLICK, EffectStrength::MEDIUM, callback);
     ASSERT_TRUE(result.isUnsupported());
-    // Callback not triggered
-    ASSERT_EQ(0, *callbackCounter.get());
 
     result = mWrapper->performEffect(Effect::CLICK, EffectStrength::STRONG, callback);
     ASSERT_TRUE(result.isFailed());
-    // Callback not triggered
-    ASSERT_EQ(0, *callbackCounter.get());
 
     result = mWrapper->performEffect(Effect::CLICK, EffectStrength::STRONG, callback);
     ASSERT_TRUE(result.isFailed());
-    // Callback not triggered
-    ASSERT_EQ(0, *callbackCounter.get());
+
+    // Callback not triggered for unsupported and on failure
+    ASSERT_EQ(1, *callbackCounter.get());
 }
 
 TEST_F(VibratorHalWrapperHidlV1_0Test, TestPerformEffectUnsupported) {
@@ -295,6 +298,7 @@
     // Using TICK that is only available in v1.1
     auto result = mWrapper->performEffect(Effect::TICK, EffectStrength::LIGHT, callback);
     ASSERT_TRUE(result.isUnsupported());
+    // No callback is triggered.
     ASSERT_EQ(0, *callbackCounter.get());
 }
 
diff --git a/services/vibratorservice/test/VibratorHalWrapperHidlV1_1Test.cpp b/services/vibratorservice/test/VibratorHalWrapperHidlV1_1Test.cpp
index d0531e6..d887efc 100644
--- a/services/vibratorservice/test/VibratorHalWrapperHidlV1_1Test.cpp
+++ b/services/vibratorservice/test/VibratorHalWrapperHidlV1_1Test.cpp
@@ -23,6 +23,7 @@
 
 #include <utils/Log.h>
 
+#include <vibratorservice/VibratorCallbackScheduler.h>
 #include <vibratorservice/VibratorHalWrapper.h>
 
 #include "test_utils.h"
@@ -58,11 +59,13 @@
 public:
     void SetUp() override {
         mMockHal = new StrictMock<MockIVibratorV1_1>();
-        mWrapper = std::make_unique<vibrator::HidlHalWrapperV1_1>(mMockHal);
+        mMockScheduler = std::make_shared<StrictMock<vibrator::MockCallbackScheduler>>();
+        mWrapper = std::make_unique<vibrator::HidlHalWrapperV1_1>(mMockScheduler, mMockHal);
         ASSERT_NE(mWrapper, nullptr);
     }
 
 protected:
+    std::shared_ptr<StrictMock<vibrator::MockCallbackScheduler>> mMockScheduler = nullptr;
     std::unique_ptr<vibrator::HalWrapper> mWrapper = nullptr;
     sp<StrictMock<MockIVibratorV1_1>> mMockHal = nullptr;
 };
@@ -70,23 +73,28 @@
 // -------------------------------------------------------------------------------------------------
 
 TEST_F(VibratorHalWrapperHidlV1_1Test, TestPerformEffectV1_0) {
-    EXPECT_CALL(*mMockHal.get(),
-                perform(Eq(V1_0::Effect::CLICK), Eq(V1_0::EffectStrength::LIGHT), _))
-            .Times(Exactly(1))
-            .WillRepeatedly(
-                    [](V1_0::Effect, V1_0::EffectStrength, MockIVibratorV1_1::perform_cb cb) {
-                        cb(V1_0::Status::OK, 100);
-                        return hardware::Return<void>();
-                    });
+    {
+        InSequence seq;
+        EXPECT_CALL(*mMockHal.get(),
+                    perform(Eq(V1_0::Effect::CLICK), Eq(V1_0::EffectStrength::LIGHT), _))
+                .Times(Exactly(1))
+                .WillRepeatedly(
+                        [](V1_0::Effect, V1_0::EffectStrength, MockIVibratorV1_1::perform_cb cb) {
+                            cb(V1_0::Status::OK, 10);
+                            return hardware::Return<void>();
+                        });
+        EXPECT_CALL(*mMockScheduler.get(), schedule(_, Eq(10ms)))
+                .Times(Exactly(1))
+                .WillRepeatedly(vibrator::TriggerSchedulerCallback());
+    }
 
     std::unique_ptr<int32_t> callbackCounter = std::make_unique<int32_t>();
     auto callback = vibrator::TestFactory::createCountingCallback(callbackCounter.get());
     auto result = mWrapper->performEffect(Effect::CLICK, EffectStrength::LIGHT, callback);
 
     ASSERT_TRUE(result.isOk());
-    ASSERT_EQ(100ms, result.value());
-    // TODO(b/153418251): check callback will be triggered once implemented
-    ASSERT_EQ(0, *callbackCounter.get());
+    ASSERT_EQ(10ms, result.value());
+    ASSERT_EQ(1, *callbackCounter.get());
 }
 
 TEST_F(VibratorHalWrapperHidlV1_1Test, TestPerformEffectV1_1) {
@@ -100,6 +108,9 @@
                     cb(V1_0::Status::OK, 10);
                     return hardware::Return<void>();
                 });
+        EXPECT_CALL(*mMockScheduler.get(), schedule(_, Eq(10ms)))
+                .Times(Exactly(1))
+                .WillRepeatedly(vibrator::TriggerSchedulerCallback());
         EXPECT_CALL(*mMockHal.get(),
                     perform_1_1(Eq(V1_1::Effect_1_1::TICK), Eq(V1_0::EffectStrength::MEDIUM), _))
                 .Times(Exactly(1))
@@ -128,23 +139,19 @@
     auto result = mWrapper->performEffect(Effect::TICK, EffectStrength::LIGHT, callback);
     ASSERT_TRUE(result.isOk());
     ASSERT_EQ(10ms, result.value());
-    // TODO(b/153418251): check callback will be triggered once implemented
-    ASSERT_EQ(0, *callbackCounter.get());
+    ASSERT_EQ(1, *callbackCounter.get());
 
     result = mWrapper->performEffect(Effect::TICK, EffectStrength::MEDIUM, callback);
     ASSERT_TRUE(result.isUnsupported());
-    // Callback not triggered
-    ASSERT_EQ(0, *callbackCounter.get());
 
     result = mWrapper->performEffect(Effect::TICK, EffectStrength::STRONG, callback);
     ASSERT_TRUE(result.isFailed());
-    // Callback not triggered
-    ASSERT_EQ(0, *callbackCounter.get());
 
     result = mWrapper->performEffect(Effect::TICK, EffectStrength::STRONG, callback);
     ASSERT_TRUE(result.isFailed());
-    // Callback not triggered
-    ASSERT_EQ(0, *callbackCounter.get());
+
+    // Callback not triggered for unsupported and on failure
+    ASSERT_EQ(1, *callbackCounter.get());
 }
 
 TEST_F(VibratorHalWrapperHidlV1_1Test, TestPerformEffectUnsupported) {
@@ -153,5 +160,6 @@
     // Using THUD that is only available in v1.2
     auto result = mWrapper->performEffect(Effect::THUD, EffectStrength::LIGHT, callback);
     ASSERT_TRUE(result.isUnsupported());
+    // No callback is triggered.
     ASSERT_EQ(0, *callbackCounter.get());
 }
diff --git a/services/vibratorservice/test/VibratorHalWrapperHidlV1_2Test.cpp b/services/vibratorservice/test/VibratorHalWrapperHidlV1_2Test.cpp
index 5d2c269..26d9350 100644
--- a/services/vibratorservice/test/VibratorHalWrapperHidlV1_2Test.cpp
+++ b/services/vibratorservice/test/VibratorHalWrapperHidlV1_2Test.cpp
@@ -23,6 +23,7 @@
 
 #include <utils/Log.h>
 
+#include <vibratorservice/VibratorCallbackScheduler.h>
 #include <vibratorservice/VibratorHalWrapper.h>
 
 #include "test_utils.h"
@@ -61,11 +62,13 @@
 public:
     void SetUp() override {
         mMockHal = new StrictMock<MockIVibratorV1_2>();
-        mWrapper = std::make_unique<vibrator::HidlHalWrapperV1_2>(mMockHal);
+        mMockScheduler = std::make_shared<StrictMock<vibrator::MockCallbackScheduler>>();
+        mWrapper = std::make_unique<vibrator::HidlHalWrapperV1_2>(mMockScheduler, mMockHal);
         ASSERT_NE(mWrapper, nullptr);
     }
 
 protected:
+    std::shared_ptr<StrictMock<vibrator::MockCallbackScheduler>> mMockScheduler = nullptr;
     std::unique_ptr<vibrator::HalWrapper> mWrapper = nullptr;
     sp<StrictMock<MockIVibratorV1_2>> mMockHal = nullptr;
 };
@@ -73,43 +76,53 @@
 // -------------------------------------------------------------------------------------------------
 
 TEST_F(VibratorHalWrapperHidlV1_2Test, TestPerformEffectV1_0) {
-    EXPECT_CALL(*mMockHal.get(),
-                perform(Eq(V1_0::Effect::CLICK), Eq(V1_0::EffectStrength::LIGHT), _))
-            .Times(Exactly(1))
-            .WillRepeatedly(
-                    [](V1_0::Effect, V1_0::EffectStrength, MockIVibratorV1_2::perform_cb cb) {
-                        cb(V1_0::Status::OK, 100);
-                        return hardware::Return<void>();
-                    });
+    {
+        InSequence seq;
+        EXPECT_CALL(*mMockHal.get(),
+                    perform(Eq(V1_0::Effect::CLICK), Eq(V1_0::EffectStrength::LIGHT), _))
+                .Times(Exactly(1))
+                .WillRepeatedly(
+                        [](V1_0::Effect, V1_0::EffectStrength, MockIVibratorV1_2::perform_cb cb) {
+                            cb(V1_0::Status::OK, 10);
+                            return hardware::Return<void>();
+                        });
+        EXPECT_CALL(*mMockScheduler.get(), schedule(_, Eq(10ms)))
+                .Times(Exactly(1))
+                .WillRepeatedly(vibrator::TriggerSchedulerCallback());
+    }
 
     std::unique_ptr<int32_t> callbackCounter = std::make_unique<int32_t>();
     auto callback = vibrator::TestFactory::createCountingCallback(callbackCounter.get());
     auto result = mWrapper->performEffect(Effect::CLICK, EffectStrength::LIGHT, callback);
 
     ASSERT_TRUE(result.isOk());
-    ASSERT_EQ(100ms, result.value());
-    // TODO(b/153418251): check callback will be triggered once implemented
-    ASSERT_EQ(0, *callbackCounter.get());
+    ASSERT_EQ(10ms, result.value());
+    ASSERT_EQ(1, *callbackCounter.get());
 }
 
 TEST_F(VibratorHalWrapperHidlV1_2Test, TestPerformEffectV1_1) {
-    EXPECT_CALL(*mMockHal.get(),
-                perform_1_1(Eq(V1_1::Effect_1_1::TICK), Eq(V1_0::EffectStrength::LIGHT), _))
-            .Times(Exactly(1))
-            .WillRepeatedly(
-                    [](V1_1::Effect_1_1, V1_0::EffectStrength, MockIVibratorV1_2::perform_cb cb) {
-                        cb(V1_0::Status::OK, 100);
-                        return hardware::Return<void>();
-                    });
+    {
+        InSequence seq;
+        EXPECT_CALL(*mMockHal.get(),
+                    perform_1_1(Eq(V1_1::Effect_1_1::TICK), Eq(V1_0::EffectStrength::LIGHT), _))
+                .Times(Exactly(1))
+                .WillRepeatedly([](V1_1::Effect_1_1, V1_0::EffectStrength,
+                                   MockIVibratorV1_2::perform_cb cb) {
+                    cb(V1_0::Status::OK, 10);
+                    return hardware::Return<void>();
+                });
+        EXPECT_CALL(*mMockScheduler.get(), schedule(_, Eq(10ms)))
+                .Times(Exactly(1))
+                .WillRepeatedly(vibrator::TriggerSchedulerCallback());
+    }
 
     std::unique_ptr<int32_t> callbackCounter = std::make_unique<int32_t>();
     auto callback = vibrator::TestFactory::createCountingCallback(callbackCounter.get());
     auto result = mWrapper->performEffect(Effect::TICK, EffectStrength::LIGHT, callback);
 
     ASSERT_TRUE(result.isOk());
-    ASSERT_EQ(100ms, result.value());
-    // TODO(b/153418251): check callback will be triggered once implemented
-    ASSERT_EQ(0, *callbackCounter.get());
+    ASSERT_EQ(10ms, result.value());
+    ASSERT_EQ(1, *callbackCounter.get());
 }
 
 TEST_F(VibratorHalWrapperHidlV1_2Test, TestPerformEffectV1_2) {
@@ -120,9 +133,12 @@
                 .Times(Exactly(1))
                 .WillRepeatedly(
                         [](V1_2::Effect, V1_0::EffectStrength, MockIVibratorV1_2::perform_cb cb) {
-                            cb(V1_0::Status::OK, 100);
+                            cb(V1_0::Status::OK, 10);
                             return hardware::Return<void>();
                         });
+        EXPECT_CALL(*mMockScheduler.get(), schedule(_, Eq(10ms)))
+                .Times(Exactly(1))
+                .WillRepeatedly(vibrator::TriggerSchedulerCallback());
         EXPECT_CALL(*mMockHal.get(),
                     perform_1_2(Eq(V1_2::Effect::THUD), Eq(V1_0::EffectStrength::MEDIUM), _))
                 .Times(Exactly(1))
@@ -149,24 +165,20 @@
 
     auto result = mWrapper->performEffect(Effect::THUD, EffectStrength::LIGHT, callback);
     ASSERT_TRUE(result.isOk());
-    ASSERT_EQ(100ms, result.value());
-    // TODO(b/153418251): check callback will be triggered once implemented
-    ASSERT_EQ(0, *callbackCounter.get());
+    ASSERT_EQ(10ms, result.value());
+    ASSERT_EQ(1, *callbackCounter.get());
 
     result = mWrapper->performEffect(Effect::THUD, EffectStrength::MEDIUM, callback);
     ASSERT_TRUE(result.isUnsupported());
-    // Callback not triggered
-    ASSERT_EQ(0, *callbackCounter.get());
 
     result = mWrapper->performEffect(Effect::THUD, EffectStrength::STRONG, callback);
     ASSERT_TRUE(result.isFailed());
-    // Callback not triggered
-    ASSERT_EQ(0, *callbackCounter.get());
 
     result = mWrapper->performEffect(Effect::THUD, EffectStrength::STRONG, callback);
     ASSERT_TRUE(result.isFailed());
-    // Callback not triggered
-    ASSERT_EQ(0, *callbackCounter.get());
+
+    // Callback not triggered for unsupported and on failure
+    ASSERT_EQ(1, *callbackCounter.get());
 }
 
 TEST_F(VibratorHalWrapperHidlV1_2Test, TestPerformEffectUnsupported) {
@@ -175,5 +187,6 @@
     // Using TEXTURE_TICK that is only available in v1.3
     auto result = mWrapper->performEffect(Effect::TEXTURE_TICK, EffectStrength::LIGHT, callback);
     ASSERT_TRUE(result.isUnsupported());
+    // No callback is triggered.
     ASSERT_EQ(0, *callbackCounter.get());
 }
diff --git a/services/vibratorservice/test/VibratorHalWrapperHidlV1_3Test.cpp b/services/vibratorservice/test/VibratorHalWrapperHidlV1_3Test.cpp
index a799257..5de6257 100644
--- a/services/vibratorservice/test/VibratorHalWrapperHidlV1_3Test.cpp
+++ b/services/vibratorservice/test/VibratorHalWrapperHidlV1_3Test.cpp
@@ -24,6 +24,7 @@
 #include <utils/Log.h>
 #include <thread>
 
+#include <vibratorservice/VibratorCallbackScheduler.h>
 #include <vibratorservice/VibratorHalWrapper.h>
 
 #include "test_utils.h"
@@ -68,11 +69,13 @@
 public:
     void SetUp() override {
         mMockHal = new StrictMock<MockIVibratorV1_3>();
-        mWrapper = std::make_unique<vibrator::HidlHalWrapperV1_3>(mMockHal);
+        mMockScheduler = std::make_shared<StrictMock<vibrator::MockCallbackScheduler>>();
+        mWrapper = std::make_unique<vibrator::HidlHalWrapperV1_3>(mMockScheduler, mMockHal);
         ASSERT_NE(mWrapper, nullptr);
     }
 
 protected:
+    std::shared_ptr<StrictMock<vibrator::MockCallbackScheduler>> mMockScheduler = nullptr;
     std::unique_ptr<vibrator::HalWrapper> mWrapper = nullptr;
     sp<StrictMock<MockIVibratorV1_3>> mMockHal = nullptr;
 };
@@ -210,6 +213,10 @@
         }));
     }
     std::for_each(threads.begin(), threads.end(), [](std::thread& t) { t.join(); });
+
+    auto result = mWrapper->getCapabilities();
+    ASSERT_TRUE(result.isOk());
+    ASSERT_EQ(vibrator::Capabilities::AMPLITUDE_CONTROL, result.value());
 }
 
 TEST_F(VibratorHalWrapperHidlV1_3Test, TestGetCapabilitiesDoesNotCacheFailedResult) {
@@ -258,63 +265,78 @@
 }
 
 TEST_F(VibratorHalWrapperHidlV1_3Test, TestPerformEffectV1_0) {
-    EXPECT_CALL(*mMockHal.get(),
-                perform(Eq(V1_0::Effect::CLICK), Eq(V1_0::EffectStrength::LIGHT), _))
-            .Times(Exactly(1))
-            .WillRepeatedly(
-                    [](V1_0::Effect, V1_0::EffectStrength, MockIVibratorV1_3::perform_cb cb) {
-                        cb(V1_0::Status::OK, 100);
-                        return hardware::Return<void>();
-                    });
+    {
+        InSequence seq;
+        EXPECT_CALL(*mMockHal.get(),
+                    perform(Eq(V1_0::Effect::CLICK), Eq(V1_0::EffectStrength::LIGHT), _))
+                .Times(Exactly(1))
+                .WillRepeatedly(
+                        [](V1_0::Effect, V1_0::EffectStrength, MockIVibratorV1_3::perform_cb cb) {
+                            cb(V1_0::Status::OK, 10);
+                            return hardware::Return<void>();
+                        });
+        EXPECT_CALL(*mMockScheduler.get(), schedule(_, Eq(10ms)))
+                .Times(Exactly(1))
+                .WillRepeatedly(vibrator::TriggerSchedulerCallback());
+    }
 
     std::unique_ptr<int32_t> callbackCounter = std::make_unique<int32_t>();
     auto callback = vibrator::TestFactory::createCountingCallback(callbackCounter.get());
     auto result = mWrapper->performEffect(Effect::CLICK, EffectStrength::LIGHT, callback);
 
     ASSERT_TRUE(result.isOk());
-    ASSERT_EQ(100ms, result.value());
-    // TODO(b/153418251): check callback will be triggered once implemented
-    ASSERT_EQ(0, *callbackCounter.get());
+    ASSERT_EQ(10ms, result.value());
+    ASSERT_EQ(1, *callbackCounter.get());
 }
 
 TEST_F(VibratorHalWrapperHidlV1_3Test, TestPerformEffectV1_1) {
-    EXPECT_CALL(*mMockHal.get(),
-                perform_1_1(Eq(V1_1::Effect_1_1::TICK), Eq(V1_0::EffectStrength::LIGHT), _))
-            .Times(Exactly(1))
-            .WillRepeatedly(
-                    [](V1_1::Effect_1_1, V1_0::EffectStrength, MockIVibratorV1_3::perform_cb cb) {
-                        cb(V1_0::Status::OK, 100);
-                        return hardware::Return<void>();
-                    });
+    {
+        InSequence seq;
+        EXPECT_CALL(*mMockHal.get(),
+                    perform_1_1(Eq(V1_1::Effect_1_1::TICK), Eq(V1_0::EffectStrength::LIGHT), _))
+                .Times(Exactly(1))
+                .WillRepeatedly([](V1_1::Effect_1_1, V1_0::EffectStrength,
+                                   MockIVibratorV1_3::perform_cb cb) {
+                    cb(V1_0::Status::OK, 10);
+                    return hardware::Return<void>();
+                });
+        EXPECT_CALL(*mMockScheduler.get(), schedule(_, Eq(10ms)))
+                .Times(Exactly(1))
+                .WillRepeatedly(vibrator::TriggerSchedulerCallback());
+    }
 
     std::unique_ptr<int32_t> callbackCounter = std::make_unique<int32_t>();
     auto callback = vibrator::TestFactory::createCountingCallback(callbackCounter.get());
     auto result = mWrapper->performEffect(Effect::TICK, EffectStrength::LIGHT, callback);
 
     ASSERT_TRUE(result.isOk());
-    ASSERT_EQ(100ms, result.value());
-    // TODO(b/153418251): check callback will be triggered once implemented
-    ASSERT_EQ(0, *callbackCounter.get());
+    ASSERT_EQ(10ms, result.value());
+    ASSERT_EQ(1, *callbackCounter.get());
 }
 
 TEST_F(VibratorHalWrapperHidlV1_3Test, TestPerformEffectV1_2) {
-    EXPECT_CALL(*mMockHal.get(),
-                perform_1_2(Eq(V1_2::Effect::THUD), Eq(V1_0::EffectStrength::LIGHT), _))
-            .Times(Exactly(1))
-            .WillRepeatedly(
-                    [](V1_2::Effect, V1_0::EffectStrength, MockIVibratorV1_3::perform_cb cb) {
-                        cb(V1_0::Status::OK, 100);
-                        return hardware::Return<void>();
-                    });
+    {
+        InSequence seq;
+        EXPECT_CALL(*mMockHal.get(),
+                    perform_1_2(Eq(V1_2::Effect::THUD), Eq(V1_0::EffectStrength::LIGHT), _))
+                .Times(Exactly(1))
+                .WillRepeatedly(
+                        [](V1_2::Effect, V1_0::EffectStrength, MockIVibratorV1_3::perform_cb cb) {
+                            cb(V1_0::Status::OK, 10);
+                            return hardware::Return<void>();
+                        });
+        EXPECT_CALL(*mMockScheduler.get(), schedule(_, Eq(10ms)))
+                .Times(Exactly(1))
+                .WillRepeatedly(vibrator::TriggerSchedulerCallback());
+    }
 
     std::unique_ptr<int32_t> callbackCounter = std::make_unique<int32_t>();
     auto callback = vibrator::TestFactory::createCountingCallback(callbackCounter.get());
     auto result = mWrapper->performEffect(Effect::THUD, EffectStrength::LIGHT, callback);
 
     ASSERT_TRUE(result.isOk());
-    ASSERT_EQ(100ms, result.value());
-    // TODO(b/153418251): check callback will be triggered once implemented
-    ASSERT_EQ(0, *callbackCounter.get());
+    ASSERT_EQ(10ms, result.value());
+    ASSERT_EQ(1, *callbackCounter.get());
 }
 
 TEST_F(VibratorHalWrapperHidlV1_3Test, TestPerformEffectV1_3) {
@@ -328,6 +350,9 @@
                             cb(V1_0::Status::OK, 10);
                             return hardware::Return<void>();
                         });
+        EXPECT_CALL(*mMockScheduler.get(), schedule(_, Eq(10ms)))
+                .Times(Exactly(1))
+                .WillRepeatedly(vibrator::TriggerSchedulerCallback());
         EXPECT_CALL(*mMockHal.get(),
                     perform_1_3(Eq(V1_3::Effect::TEXTURE_TICK), Eq(V1_0::EffectStrength::MEDIUM),
                                 _))
@@ -357,21 +382,17 @@
     auto result = mWrapper->performEffect(Effect::TEXTURE_TICK, EffectStrength::LIGHT, callback);
     ASSERT_TRUE(result.isOk());
     ASSERT_EQ(10ms, result.value());
-    // TODO(b/153418251): check callback will be triggered once implemented
-    ASSERT_EQ(0, *callbackCounter.get());
+    ASSERT_EQ(1, *callbackCounter.get());
 
     result = mWrapper->performEffect(Effect::TEXTURE_TICK, EffectStrength::MEDIUM, callback);
     ASSERT_TRUE(result.isUnsupported());
-    // Callback not triggered
-    ASSERT_EQ(0, *callbackCounter.get());
 
     result = mWrapper->performEffect(Effect::TEXTURE_TICK, EffectStrength::STRONG, callback);
     ASSERT_TRUE(result.isFailed());
-    // Callback not triggered
-    ASSERT_EQ(0, *callbackCounter.get());
 
     result = mWrapper->performEffect(Effect::TEXTURE_TICK, EffectStrength::STRONG, callback);
     ASSERT_TRUE(result.isFailed());
-    // Callback not triggered
-    ASSERT_EQ(0, *callbackCounter.get());
+
+    // Callback not triggered for unsupported and on failure
+    ASSERT_EQ(1, *callbackCounter.get());
 }
diff --git a/services/vibratorservice/test/test_utils.h b/services/vibratorservice/test/test_utils.h
index fc9b364..8d0b22e 100644
--- a/services/vibratorservice/test/test_utils.h
+++ b/services/vibratorservice/test/test_utils.h
@@ -28,6 +28,20 @@
 using ::android::hardware::vibrator::CompositeEffect;
 using ::android::hardware::vibrator::CompositePrimitive;
 
+// -------------------------------------------------------------------------------------------------
+
+class MockCallbackScheduler : public vibrator::CallbackScheduler {
+public:
+    MOCK_METHOD(void, schedule, (std::function<void()> callback, std::chrono::milliseconds delay),
+                (override));
+};
+
+ACTION(TriggerSchedulerCallback) {
+    arg0();
+}
+
+// -------------------------------------------------------------------------------------------------
+
 class TestFactory {
 public:
     static CompositeEffect createCompositeEffect(CompositePrimitive primitive,
@@ -48,6 +62,8 @@
     ~TestFactory() = delete;
 };
 
+// -------------------------------------------------------------------------------------------------
+
 } // namespace vibrator
 
 } // namespace android