Merge "Introduce vibration session HAL support" into main
diff --git a/vibrator/aidl/aidl_api/android.hardware.vibrator/current/android/hardware/vibrator/IVibrationSession.aidl b/vibrator/aidl/aidl_api/android.hardware.vibrator/current/android/hardware/vibrator/IVibrationSession.aidl
new file mode 100644
index 0000000..ec301b2
--- /dev/null
+++ b/vibrator/aidl/aidl_api/android.hardware.vibrator/current/android/hardware/vibrator/IVibrationSession.aidl
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2024 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.hardware.vibrator;
+@VintfStability
+interface IVibrationSession {
+  void close();
+  void abort();
+}
diff --git a/vibrator/aidl/aidl_api/android.hardware.vibrator/current/android/hardware/vibrator/IVibratorManager.aidl b/vibrator/aidl/aidl_api/android.hardware.vibrator/current/android/hardware/vibrator/IVibratorManager.aidl
index ef5794c..081d9dc 100644
--- a/vibrator/aidl/aidl_api/android.hardware.vibrator/current/android/hardware/vibrator/IVibratorManager.aidl
+++ b/vibrator/aidl/aidl_api/android.hardware.vibrator/current/android/hardware/vibrator/IVibratorManager.aidl
@@ -40,6 +40,8 @@
   void prepareSynced(in int[] vibratorIds);
   void triggerSynced(in android.hardware.vibrator.IVibratorCallback callback);
   void cancelSynced();
+  android.hardware.vibrator.IVibrationSession startSession(in int[] vibratorIds, in android.hardware.vibrator.VibrationSessionConfig config, in android.hardware.vibrator.IVibratorCallback callback);
+  void clearSessions();
   const int CAP_SYNC = (1 << 0) /* 1 */;
   const int CAP_PREPARE_ON = (1 << 1) /* 2 */;
   const int CAP_PREPARE_PERFORM = (1 << 2) /* 4 */;
@@ -48,4 +50,5 @@
   const int CAP_MIXED_TRIGGER_PERFORM = (1 << 5) /* 32 */;
   const int CAP_MIXED_TRIGGER_COMPOSE = (1 << 6) /* 64 */;
   const int CAP_TRIGGER_CALLBACK = (1 << 7) /* 128 */;
+  const int CAP_START_SESSIONS = (1 << 8) /* 256 */;
 }
diff --git a/vibrator/aidl/aidl_api/android.hardware.vibrator/current/android/hardware/vibrator/VibrationSessionConfig.aidl b/vibrator/aidl/aidl_api/android.hardware.vibrator/current/android/hardware/vibrator/VibrationSessionConfig.aidl
new file mode 100644
index 0000000..01136aa
--- /dev/null
+++ b/vibrator/aidl/aidl_api/android.hardware.vibrator/current/android/hardware/vibrator/VibrationSessionConfig.aidl
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2024 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.hardware.vibrator;
+@VintfStability
+parcelable VibrationSessionConfig {
+  ParcelableHolder vendorExtension;
+}
diff --git a/vibrator/aidl/android/hardware/vibrator/IVibrationSession.aidl b/vibrator/aidl/android/hardware/vibrator/IVibrationSession.aidl
new file mode 100644
index 0000000..88382e5
--- /dev/null
+++ b/vibrator/aidl/android/hardware/vibrator/IVibrationSession.aidl
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2024 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.
+ */
+
+package android.hardware.vibrator;
+
+@VintfStability
+interface IVibrationSession {
+    /**
+     * Request the end of this session.
+     *
+     * This will cause this session to end once the ongoing vibration commands are completed in each
+     * individual vibrator. The immediate end of this session can stll be trigged via abort().
+     *
+     * This should not block on the end of this session. The callback provided during the creation
+     * of this session should be used to indicate the vibrations are done and the session has
+     * ended. The session object can be safely destroyed after this is called, and the session
+     * should end as expected.
+     */
+    void close();
+
+    /**
+     * Immediately end this session.
+     *
+     * This will cause this session to end immediately and stop any ongoing vibration. The vibrator
+     * manager and each individual vibrator in this session will be reset and available when this
+     * returns.
+     */
+    void abort();
+}
diff --git a/vibrator/aidl/android/hardware/vibrator/IVibratorManager.aidl b/vibrator/aidl/android/hardware/vibrator/IVibratorManager.aidl
index eb5e9cc..e8b85c5 100644
--- a/vibrator/aidl/android/hardware/vibrator/IVibratorManager.aidl
+++ b/vibrator/aidl/android/hardware/vibrator/IVibratorManager.aidl
@@ -16,8 +16,10 @@
 
 package android.hardware.vibrator;
 
+import android.hardware.vibrator.IVibrationSession;
 import android.hardware.vibrator.IVibrator;
 import android.hardware.vibrator.IVibratorCallback;
+import android.hardware.vibrator.VibrationSessionConfig;
 
 @VintfStability
 interface IVibratorManager {
@@ -42,17 +44,23 @@
      */
     const int CAP_MIXED_TRIGGER_ON = 1 << 4;
     /**
-     * Whether IVibrator 'perform' can be triggered with other functions in sync with 'triggerSynced'.
+     * Whether IVibrator 'perform' can be triggered with other functions in sync with
+     * 'triggerSynced'.
      */
     const int CAP_MIXED_TRIGGER_PERFORM = 1 << 5;
     /**
-     * Whether IVibrator 'compose' can be triggered with other functions in sync with 'triggerSynced'.
+     * Whether IVibrator 'compose' can be triggered with other functions in sync with
+     * 'triggerSynced'.
      */
     const int CAP_MIXED_TRIGGER_COMPOSE = 1 << 6;
     /**
      * Whether on w/ IVibratorCallback can be used w/ 'trigerSynced' function.
      */
     const int CAP_TRIGGER_CALLBACK = 1 << 7;
+    /**
+     * Whether vibration sessions are supported.
+     */
+    const int CAP_START_SESSIONS = 1 << 8;
 
     /**
      * Determine capabilities of the vibrator manager HAL (CAP_* mask)
@@ -75,8 +83,8 @@
      * This function must only be called after the previous synced vibration was triggered or
      * canceled (through cancelSynced()).
      *
-     * Doing this operation while any of the specified vibrators is already on is undefined behavior.
-     * Clients should explicitly call off in each vibrator.
+     * Doing this operation while any of the specified vibrators is already on is undefined
+     * behavior. Clients should explicitly call off in each vibrator.
      *
      * @param vibratorIds ids of the vibrators to play vibrations in sync.
      */
@@ -99,4 +107,41 @@
      * Cancel a previously-started preparation for synced vibration, if any.
      */
     void cancelSynced();
+
+    /**
+     * Start a vibration session.
+     *
+     * A vibration session can be used to send commands without resetting the vibrator state. Once a
+     * session starts, the individual vibrators can receive one or more commands like on(),
+     * performEffect(), setAmplitude(), etc. The vibrations performed in a session must have the
+     * same behavior they have outside them. Multiple commands can be synced in a session via
+     * prepareSynced as usual.
+     *
+     * Starting a session on a vibrator already in another session or in a prepareSynced state is
+     * not allowed and should throw illegal state. The end of a session should always notify the
+     * callback provided, even if it ends prematurely due to an error.
+     *
+     * This may not be supported and this support is reflected in
+     * getCapabilities (CAP_START_SESSIONS). IVibratorCallback.onComplete() support is required for
+     * this API.
+     *
+     * @param vibratorIds ids of the vibrators in the session.
+     * @param config The parameters for starting a vibration session.
+     * @param callback A callback used to inform Frameworks of state change.
+     * @throws :
+     *         - EX_UNSUPPORTED_OPERATION if unsupported, as reflected by getCapabilities.
+     *         - EX_ILLEGAL_ARGUMENT for invalid vibrator IDs.
+     *         - EX_ILLEGAL_STATE for vibrator IDs already in a session or in a prepareSynced state.
+     *         - EX_SERVICE_SPECIFIC for bad vendor data.
+     */
+    IVibrationSession startSession(
+            in int[] vibratorIds, in VibrationSessionConfig config, in IVibratorCallback callback);
+
+    /**
+     * Abort and clear all ongoing vibration sessions.
+     *
+     * This can be used to reset the vibrator manager and some individual vibrators to an idle
+     * state.
+     */
+    void clearSessions();
 }
diff --git a/vibrator/aidl/android/hardware/vibrator/VibrationSessionConfig.aidl b/vibrator/aidl/android/hardware/vibrator/VibrationSessionConfig.aidl
new file mode 100644
index 0000000..56cdde3
--- /dev/null
+++ b/vibrator/aidl/android/hardware/vibrator/VibrationSessionConfig.aidl
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2024 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.
+ */
+
+package android.hardware.vibrator;
+
+@VintfStability
+parcelable VibrationSessionConfig {
+    /**
+     * Vendor extension point for starting a vibration session.
+     */
+    ParcelableHolder vendorExtension;
+}
diff --git a/vibrator/aidl/default/Android.bp b/vibrator/aidl/default/Android.bp
index 4b26640..0ac5bc0 100644
--- a/vibrator/aidl/default/Android.bp
+++ b/vibrator/aidl/default/Android.bp
@@ -19,6 +19,7 @@
     ],
     export_include_dirs: ["include"],
     srcs: [
+        "VibrationSession.cpp",
         "Vibrator.cpp",
         "VibratorManager.cpp",
     ],
diff --git a/vibrator/aidl/default/VibrationSession.cpp b/vibrator/aidl/default/VibrationSession.cpp
new file mode 100644
index 0000000..cfb6608
--- /dev/null
+++ b/vibrator/aidl/default/VibrationSession.cpp
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2024 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 "vibrator-impl/VibrationSession.h"
+
+#include <android-base/logging.h>
+#include <thread>
+
+namespace aidl {
+namespace android {
+namespace hardware {
+namespace vibrator {
+
+static constexpr int32_t SESSION_END_DELAY_MS = 50;
+
+ndk::ScopedAStatus VibrationSession::close() {
+    LOG(VERBOSE) << "Vibration Session close";
+    mManager->closeSession(SESSION_END_DELAY_MS);
+    return ndk::ScopedAStatus::ok();
+}
+
+ndk::ScopedAStatus VibrationSession::abort() {
+    LOG(VERBOSE) << "Vibration Session abort";
+    mManager->abortSession();
+    return ndk::ScopedAStatus::ok();
+}
+
+}  // namespace vibrator
+}  // namespace hardware
+}  // namespace android
+}  // namespace aidl
diff --git a/vibrator/aidl/default/Vibrator.cpp b/vibrator/aidl/default/Vibrator.cpp
index 4f8c2b8..34be008 100644
--- a/vibrator/aidl/default/Vibrator.cpp
+++ b/vibrator/aidl/default/Vibrator.cpp
@@ -45,11 +45,62 @@
 // Service specific error code used for vendor vibration effects.
 static constexpr int32_t ERROR_CODE_INVALID_DURATION = 1;
 
+void Vibrator::dispatchVibrate(int32_t timeoutMs,
+                               const std::shared_ptr<IVibratorCallback>& callback) {
+    std::lock_guard lock(mMutex);
+    if (mIsVibrating) {
+        // Already vibrating, ignore new request.
+        return;
+    }
+    mVibrationCallback = callback;
+    mIsVibrating = true;
+    // Note that thread lambdas aren't using implicit capture [=], to avoid capturing "this",
+    // which may be asynchronously destructed.
+    std::thread([timeoutMs, callback, sharedThis = this->ref<Vibrator>()] {
+        LOG(VERBOSE) << "Starting delayed callback on another thread";
+        usleep(timeoutMs * 1000);
+
+        if (sharedThis) {
+            std::lock_guard lock(sharedThis->mMutex);
+            sharedThis->mIsVibrating = false;
+            if (sharedThis->mVibrationCallback && (callback == sharedThis->mVibrationCallback)) {
+                LOG(VERBOSE) << "Notifying callback onComplete";
+                if (!sharedThis->mVibrationCallback->onComplete().isOk()) {
+                    LOG(ERROR) << "Failed to call onComplete";
+                }
+                sharedThis->mVibrationCallback = nullptr;
+            }
+            if (sharedThis->mGlobalVibrationCallback) {
+                LOG(VERBOSE) << "Notifying global callback onComplete";
+                if (!sharedThis->mGlobalVibrationCallback->onComplete().isOk()) {
+                    LOG(ERROR) << "Failed to call onComplete";
+                }
+                sharedThis->mGlobalVibrationCallback = nullptr;
+            }
+        }
+    }).detach();
+}
+
+void Vibrator::setGlobalVibrationCallback(const std::shared_ptr<IVibratorCallback>& callback) {
+    std::lock_guard lock(mMutex);
+    if (mIsVibrating) {
+        mGlobalVibrationCallback = callback;
+    } else if (callback) {
+        std::thread([callback] {
+            LOG(VERBOSE) << "Notifying global callback onComplete";
+            if (!callback->onComplete().isOk()) {
+                LOG(ERROR) << "Failed to call onComplete";
+            }
+        }).detach();
+    }
+}
+
 ndk::ScopedAStatus Vibrator::getCapabilities(int32_t* _aidl_return) {
     LOG(VERBOSE) << "Vibrator reporting capabilities";
     std::lock_guard lock(mMutex);
     if (mCapabilities == 0) {
-        if (!getInterfaceVersion(&mVersion).isOk()) {
+        int32_t version;
+        if (!getInterfaceVersion(&version).isOk()) {
             return ndk::ScopedAStatus(AStatus_fromExceptionCode(EX_ILLEGAL_STATE));
         }
         mCapabilities = IVibrator::CAP_ON_CALLBACK | IVibrator::CAP_PERFORM_CALLBACK |
@@ -59,9 +110,9 @@
                         IVibrator::CAP_GET_Q_FACTOR | IVibrator::CAP_FREQUENCY_CONTROL |
                         IVibrator::CAP_COMPOSE_PWLE_EFFECTS;
 
-        if (mVersion >= 3) {
-            mCapabilities |= (IVibrator::CAP_PERFORM_VENDOR_EFFECTS |
-                              IVibrator::CAP_COMPOSE_PWLE_EFFECTS_V2);
+        if (version >= 3) {
+            mCapabilities |=
+                    IVibrator::CAP_PERFORM_VENDOR_EFFECTS | IVibrator::CAP_COMPOSE_PWLE_EFFECTS_V2;
         }
     }
 
@@ -71,25 +122,35 @@
 
 ndk::ScopedAStatus Vibrator::off() {
     LOG(VERBOSE) << "Vibrator off";
+    std::lock_guard lock(mMutex);
+    std::shared_ptr<IVibratorCallback> callback = mVibrationCallback;
+    std::shared_ptr<IVibratorCallback> globalCallback = mGlobalVibrationCallback;
+    mIsVibrating = false;
+    mVibrationCallback = nullptr;
+    mGlobalVibrationCallback = nullptr;
+    if (callback || globalCallback) {
+        std::thread([callback, globalCallback] {
+            if (callback) {
+                LOG(VERBOSE) << "Notifying callback onComplete";
+                if (!callback->onComplete().isOk()) {
+                    LOG(ERROR) << "Failed to call onComplete";
+                }
+            }
+            if (globalCallback) {
+                LOG(VERBOSE) << "Notifying global callback onComplete";
+                if (!globalCallback->onComplete().isOk()) {
+                    LOG(ERROR) << "Failed to call onComplete";
+                }
+            }
+        }).detach();
+    }
     return ndk::ScopedAStatus::ok();
 }
 
 ndk::ScopedAStatus Vibrator::on(int32_t timeoutMs,
                                 const std::shared_ptr<IVibratorCallback>& callback) {
     LOG(VERBOSE) << "Vibrator on for timeoutMs: " << timeoutMs;
-    if (callback != nullptr) {
-        // Note that thread lambdas aren't using implicit capture [=], to avoid capturing "this",
-        // which may be asynchronously destructed.
-        // If "this" is needed, use [sharedThis = this->ref<Vibrator>()].
-        std::thread([timeoutMs, callback] {
-            LOG(VERBOSE) << "Starting on on another thread";
-            usleep(timeoutMs * 1000);
-            LOG(VERBOSE) << "Notifying on complete";
-            if (!callback->onComplete().isOk()) {
-                LOG(ERROR) << "Failed to call onComplete";
-            }
-        }).detach();
-    }
+    dispatchVibrate(timeoutMs, callback);
     return ndk::ScopedAStatus::ok();
 }
 
@@ -107,16 +168,7 @@
     }
 
     constexpr size_t kEffectMillis = 100;
-
-    if (callback != nullptr) {
-        std::thread([callback] {
-            LOG(VERBOSE) << "Starting perform on another thread";
-            usleep(kEffectMillis * 1000);
-            LOG(VERBOSE) << "Notifying perform complete";
-            callback->onComplete();
-        }).detach();
-    }
-
+    dispatchVibrate(kEffectMillis, callback);
     *_aidl_return = kEffectMillis;
     return ndk::ScopedAStatus::ok();
 }
@@ -150,15 +202,7 @@
         return ndk::ScopedAStatus::fromServiceSpecificError(ERROR_CODE_INVALID_DURATION);
     }
 
-    if (callback != nullptr) {
-        std::thread([callback, durationMs] {
-            LOG(VERBOSE) << "Starting perform on another thread for durationMs:" << durationMs;
-            usleep(durationMs * 1000);
-            LOG(VERBOSE) << "Notifying perform vendor effect complete";
-            callback->onComplete();
-        }).detach();
-    }
-
+    dispatchVibrate(durationMs, callback);
     return ndk::ScopedAStatus::ok();
 }
 
@@ -237,28 +281,14 @@
         }
     }
 
-    // The thread may theoretically outlive the vibrator, so take a proper reference to it.
-    std::thread([sharedThis = this->ref<Vibrator>(), composite, callback] {
-        LOG(VERBOSE) << "Starting compose on another thread";
+    int32_t totalDuration = 0;
+    for (auto& e : composite) {
+        int32_t durationMs;
+        getPrimitiveDuration(e.primitive, &durationMs);
+        totalDuration += e.delayMs + durationMs;
+    }
 
-        for (auto& e : composite) {
-            if (e.delayMs) {
-                usleep(e.delayMs * 1000);
-            }
-            LOG(VERBOSE) << "triggering primitive " << static_cast<int>(e.primitive) << " @ scale "
-                         << e.scale;
-
-            int32_t durationMs;
-            sharedThis->getPrimitiveDuration(e.primitive, &durationMs);
-            usleep(durationMs * 1000);
-        }
-
-        if (callback != nullptr) {
-            LOG(VERBOSE) << "Notifying perform complete";
-            callback->onComplete();
-        }
-    }).detach();
-
+    dispatchVibrate(totalDuration, callback);
     return ndk::ScopedAStatus::ok();
 }
 
@@ -460,15 +490,7 @@
         }
     }
 
-    std::thread([totalDuration, callback] {
-        LOG(VERBOSE) << "Starting composePwle on another thread";
-        usleep(totalDuration * 1000);
-        if (callback != nullptr) {
-            LOG(VERBOSE) << "Notifying compose PWLE complete";
-            callback->onComplete();
-        }
-    }).detach();
-
+    dispatchVibrate(totalDuration, callback);
     return ndk::ScopedAStatus::ok();
 }
 
@@ -543,6 +565,7 @@
 
 ndk::ScopedAStatus Vibrator::composePwleV2(const std::vector<PwleV2Primitive>& composite,
                                            const std::shared_ptr<IVibratorCallback>& callback) {
+    LOG(VERBOSE) << "Vibrator compose PWLE V2";
     int32_t capabilities = 0;
     if (!getCapabilities(&capabilities).isOk()) {
         return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE);
@@ -576,15 +599,7 @@
         totalEffectDuration += e.timeMillis;
     }
 
-    std::thread([totalEffectDuration, callback] {
-        LOG(VERBOSE) << "Starting composePwleV2 on another thread";
-        usleep(totalEffectDuration * 1000);
-        if (callback != nullptr) {
-            LOG(VERBOSE) << "Notifying compose PWLE V2 complete";
-            callback->onComplete();
-        }
-    }).detach();
-
+    dispatchVibrate(totalEffectDuration, callback);
     return ndk::ScopedAStatus::ok();
 }
 
diff --git a/vibrator/aidl/default/VibratorManager.cpp b/vibrator/aidl/default/VibratorManager.cpp
index 26edf5a..c3be468 100644
--- a/vibrator/aidl/default/VibratorManager.cpp
+++ b/vibrator/aidl/default/VibratorManager.cpp
@@ -15,6 +15,9 @@
  */
 
 #include "vibrator-impl/VibratorManager.h"
+#include "vibrator-impl/VibrationSession.h"
+
+#include <aidl/android/hardware/vibrator/BnVibratorCallback.h>
 
 #include <android-base/logging.h>
 #include <thread>
@@ -26,25 +29,52 @@
 
 static constexpr int32_t kDefaultVibratorId = 1;
 
+class VibratorCallback : public BnVibratorCallback {
+  public:
+    VibratorCallback(const std::function<void()>& callback) : mCallback(callback) {}
+    ndk::ScopedAStatus onComplete() override {
+        mCallback();
+        return ndk::ScopedAStatus::ok();
+    }
+
+  private:
+    std::function<void()> mCallback;
+};
+
 ndk::ScopedAStatus VibratorManager::getCapabilities(int32_t* _aidl_return) {
-    LOG(INFO) << "Vibrator manager reporting capabilities";
-    *_aidl_return =
-            IVibratorManager::CAP_SYNC | IVibratorManager::CAP_PREPARE_ON |
-            IVibratorManager::CAP_PREPARE_PERFORM | IVibratorManager::CAP_PREPARE_COMPOSE |
-            IVibratorManager::CAP_MIXED_TRIGGER_ON | IVibratorManager::CAP_MIXED_TRIGGER_PERFORM |
-            IVibratorManager::CAP_MIXED_TRIGGER_COMPOSE | IVibratorManager::CAP_TRIGGER_CALLBACK;
+    LOG(VERBOSE) << "Vibrator manager reporting capabilities";
+    std::lock_guard lock(mMutex);
+    if (mCapabilities == 0) {
+        int32_t version;
+        if (!getInterfaceVersion(&version).isOk()) {
+            return ndk::ScopedAStatus(AStatus_fromExceptionCode(EX_ILLEGAL_STATE));
+        }
+        mCapabilities = IVibratorManager::CAP_SYNC | IVibratorManager::CAP_PREPARE_ON |
+                        IVibratorManager::CAP_PREPARE_PERFORM |
+                        IVibratorManager::CAP_PREPARE_COMPOSE |
+                        IVibratorManager::CAP_MIXED_TRIGGER_ON |
+                        IVibratorManager::CAP_MIXED_TRIGGER_PERFORM |
+                        IVibratorManager::CAP_MIXED_TRIGGER_COMPOSE |
+                        IVibratorManager::CAP_TRIGGER_CALLBACK;
+
+        if (version >= 3) {
+            mCapabilities |= IVibratorManager::CAP_START_SESSIONS;
+        }
+    }
+
+    *_aidl_return = mCapabilities;
     return ndk::ScopedAStatus::ok();
 }
 
 ndk::ScopedAStatus VibratorManager::getVibratorIds(std::vector<int32_t>* _aidl_return) {
-    LOG(INFO) << "Vibrator manager getting vibrator ids";
+    LOG(VERBOSE) << "Vibrator manager getting vibrator ids";
     *_aidl_return = {kDefaultVibratorId};
     return ndk::ScopedAStatus::ok();
 }
 
 ndk::ScopedAStatus VibratorManager::getVibrator(int32_t vibratorId,
                                                 std::shared_ptr<IVibrator>* _aidl_return) {
-    LOG(INFO) << "Vibrator manager getting vibrator " << vibratorId;
+    LOG(VERBOSE) << "Vibrator manager getting vibrator " << vibratorId;
     if (vibratorId == kDefaultVibratorId) {
         *_aidl_return = mDefaultVibrator;
         return ndk::ScopedAStatus::ok();
@@ -55,32 +85,131 @@
 }
 
 ndk::ScopedAStatus VibratorManager::prepareSynced(const std::vector<int32_t>& vibratorIds) {
-    LOG(INFO) << "Vibrator Manager prepare synced";
-    if (vibratorIds.size() == 1 && vibratorIds[0] == kDefaultVibratorId) {
-        return ndk::ScopedAStatus::ok();
-    } else {
+    LOG(VERBOSE) << "Vibrator Manager prepare synced";
+    if (vibratorIds.size() != 1 || vibratorIds[0] != kDefaultVibratorId) {
         return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
     }
+    std::lock_guard lock(mMutex);
+    if (mIsPreparing) {
+        return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE);
+    }
+    mIsPreparing = true;
+    return ndk::ScopedAStatus::ok();
 }
 
 ndk::ScopedAStatus VibratorManager::triggerSynced(
         const std::shared_ptr<IVibratorCallback>& callback) {
-    LOG(INFO) << "Vibrator Manager trigger synced";
+    LOG(VERBOSE) << "Vibrator Manager trigger synced";
+    std::lock_guard lock(mMutex);
+    if (!mIsPreparing) {
+        return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE);
+    }
     std::thread([callback] {
         if (callback != nullptr) {
-            LOG(INFO) << "Notifying perform complete";
+            LOG(VERBOSE) << "Notifying perform complete";
             callback->onComplete();
         }
     }).detach();
-
+    mIsPreparing = false;
     return ndk::ScopedAStatus::ok();
 }
 
 ndk::ScopedAStatus VibratorManager::cancelSynced() {
-    LOG(INFO) << "Vibrator Manager cancel synced";
+    LOG(VERBOSE) << "Vibrator Manager cancel synced";
+    std::lock_guard lock(mMutex);
+    mIsPreparing = false;
     return ndk::ScopedAStatus::ok();
 }
 
+ndk::ScopedAStatus VibratorManager::startSession(const std::vector<int32_t>& vibratorIds,
+                                                 const VibrationSessionConfig&,
+                                                 const std::shared_ptr<IVibratorCallback>& callback,
+                                                 std::shared_ptr<IVibrationSession>* _aidl_return) {
+    LOG(VERBOSE) << "Vibrator Manager start session";
+    *_aidl_return = nullptr;
+    int32_t capabilities = 0;
+    if (!getCapabilities(&capabilities).isOk()) {
+        return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE);
+    }
+    if ((capabilities & IVibratorManager::CAP_START_SESSIONS) == 0) {
+        return ndk::ScopedAStatus(AStatus_fromExceptionCode(EX_UNSUPPORTED_OPERATION));
+    }
+    if (vibratorIds.size() != 1 || vibratorIds[0] != kDefaultVibratorId) {
+        return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
+    }
+    std::lock_guard lock(mMutex);
+    if (mIsPreparing || mSession) {
+        return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE);
+    }
+    mSessionCallback = callback;
+    mSession = ndk::SharedRefBase::make<VibrationSession>(this->ref<VibratorManager>());
+    *_aidl_return = static_cast<std::shared_ptr<IVibrationSession>>(mSession);
+    return ndk::ScopedAStatus::ok();
+}
+
+ndk::ScopedAStatus VibratorManager::clearSessions() {
+    LOG(VERBOSE) << "Vibrator Manager clear sessions";
+    abortSession();
+    return ndk::ScopedAStatus::ok();
+}
+
+void VibratorManager::abortSession() {
+    std::shared_ptr<IVibrationSession> session;
+    {
+        std::lock_guard lock(mMutex);
+        session = mSession;
+    }
+    if (session) {
+        mDefaultVibrator->off();
+        clearSession(session);
+    }
+}
+
+void VibratorManager::closeSession(int32_t delayMs) {
+    std::shared_ptr<IVibrationSession> session;
+    {
+        std::lock_guard lock(mMutex);
+        if (mIsClosingSession) {
+            // Already closing session, ignore this.
+            return;
+        }
+        session = mSession;
+        mIsClosingSession = true;
+    }
+    if (session) {
+        auto callback = ndk::SharedRefBase::make<VibratorCallback>(
+                [session, delayMs, sharedThis = this->ref<VibratorManager>()] {
+                    LOG(VERBOSE) << "Closing session after vibrator became idle";
+                    usleep(delayMs * 1000);
+
+                    if (sharedThis) {
+                        sharedThis->clearSession(session);
+                    }
+                });
+        mDefaultVibrator->setGlobalVibrationCallback(callback);
+    }
+}
+
+void VibratorManager::clearSession(const std::shared_ptr<IVibrationSession>& session) {
+    std::lock_guard lock(mMutex);
+    if (mSession != session) {
+        // Probably a delayed call from an old session that was already cleared, ignore it.
+        return;
+    }
+    std::shared_ptr<IVibratorCallback> callback = mSessionCallback;
+    mSession = nullptr;
+    mSessionCallback = nullptr;  // make sure any delayed call will not trigger this again.
+    mIsClosingSession = false;
+    if (callback) {
+        std::thread([callback] {
+            LOG(VERBOSE) << "Notifying session complete";
+            if (!callback->onComplete().isOk()) {
+                LOG(ERROR) << "Failed to call onComplete";
+            }
+        }).detach();
+    }
+}
+
 }  // namespace vibrator
 }  // namespace hardware
 }  // namespace android
diff --git a/vibrator/aidl/default/include/vibrator-impl/VibrationSession.h b/vibrator/aidl/default/include/vibrator-impl/VibrationSession.h
new file mode 100644
index 0000000..98bdd1c
--- /dev/null
+++ b/vibrator/aidl/default/include/vibrator-impl/VibrationSession.h
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2024 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.
+ */
+
+#pragma once
+
+#include <aidl/android/hardware/vibrator/BnVibrationSession.h>
+#include <aidl/android/hardware/vibrator/IVibrator.h>
+#include <aidl/android/hardware/vibrator/IVibratorCallback.h>
+#include <android-base/thread_annotations.h>
+
+#include "vibrator-impl/VibratorManager.h"
+
+namespace aidl {
+namespace android {
+namespace hardware {
+namespace vibrator {
+
+class VibrationSession : public BnVibrationSession {
+  public:
+    VibrationSession(std::shared_ptr<VibratorManager> manager) : mManager(std::move(manager)) {};
+
+    ndk::ScopedAStatus close() override;
+    ndk::ScopedAStatus abort() override;
+
+  private:
+    mutable std::mutex mMutex;
+    std::shared_ptr<VibratorManager> mManager;
+};
+
+}  // namespace vibrator
+}  // namespace hardware
+}  // namespace android
+}  // namespace aidl
diff --git a/vibrator/aidl/default/include/vibrator-impl/Vibrator.h b/vibrator/aidl/default/include/vibrator-impl/Vibrator.h
index 28bc763..4637c5a 100644
--- a/vibrator/aidl/default/include/vibrator-impl/Vibrator.h
+++ b/vibrator/aidl/default/include/vibrator-impl/Vibrator.h
@@ -25,6 +25,7 @@
 namespace vibrator {
 
 class Vibrator : public BnVibrator {
+  public:
     ndk::ScopedAStatus getCapabilities(int32_t* _aidl_return) override;
     ndk::ScopedAStatus off() override;
     ndk::ScopedAStatus on(int32_t timeoutMs,
@@ -66,10 +67,16 @@
     ndk::ScopedAStatus composePwleV2(const std::vector<PwleV2Primitive>& composite,
                                      const std::shared_ptr<IVibratorCallback>& callback) override;
 
+    void setGlobalVibrationCallback(const std::shared_ptr<IVibratorCallback>& callback);
+
   private:
     mutable std::mutex mMutex;
-    int32_t mVersion GUARDED_BY(mMutex) = 0;  // current Hal version
+    bool mIsVibrating GUARDED_BY(mMutex) = false;
     int32_t mCapabilities GUARDED_BY(mMutex) = 0;
+    std::shared_ptr<IVibratorCallback> mVibrationCallback GUARDED_BY(mMutex) = nullptr;
+    std::shared_ptr<IVibratorCallback> mGlobalVibrationCallback GUARDED_BY(mMutex) = nullptr;
+
+    void dispatchVibrate(int32_t timeoutMs, const std::shared_ptr<IVibratorCallback>& callback);
 };
 
 }  // namespace vibrator
diff --git a/vibrator/aidl/default/include/vibrator-impl/VibratorManager.h b/vibrator/aidl/default/include/vibrator-impl/VibratorManager.h
index 319eb05..fe30394 100644
--- a/vibrator/aidl/default/include/vibrator-impl/VibratorManager.h
+++ b/vibrator/aidl/default/include/vibrator-impl/VibratorManager.h
@@ -17,6 +17,9 @@
 #pragma once
 
 #include <aidl/android/hardware/vibrator/BnVibratorManager.h>
+#include <android-base/thread_annotations.h>
+
+#include "vibrator-impl/Vibrator.h"
 
 namespace aidl {
 namespace android {
@@ -25,7 +28,8 @@
 
 class VibratorManager : public BnVibratorManager {
   public:
-    VibratorManager(std::shared_ptr<IVibrator> vibrator) : mDefaultVibrator(std::move(vibrator)){};
+    VibratorManager(std::shared_ptr<Vibrator> vibrator) : mDefaultVibrator(std::move(vibrator)) {};
+
     ndk::ScopedAStatus getCapabilities(int32_t* _aidl_return) override;
     ndk::ScopedAStatus getVibratorIds(std::vector<int32_t>* _aidl_return) override;
     ndk::ScopedAStatus getVibrator(int32_t vibratorId,
@@ -33,9 +37,25 @@
     ndk::ScopedAStatus prepareSynced(const std::vector<int32_t>& vibratorIds) override;
     ndk::ScopedAStatus triggerSynced(const std::shared_ptr<IVibratorCallback>& callback) override;
     ndk::ScopedAStatus cancelSynced() override;
+    ndk::ScopedAStatus startSession(const std::vector<int32_t>& vibratorIds,
+                                    const VibrationSessionConfig& config,
+                                    const std::shared_ptr<IVibratorCallback>& callback,
+                                    std::shared_ptr<IVibrationSession>* _aidl_return) override;
+    ndk::ScopedAStatus clearSessions() override;
+
+    void abortSession();
+    void closeSession(int32_t delayMs);
 
   private:
-    std::shared_ptr<IVibrator> mDefaultVibrator;
+    std::shared_ptr<Vibrator> mDefaultVibrator;
+    mutable std::mutex mMutex;
+    int32_t mCapabilities GUARDED_BY(mMutex) = 0;
+    bool mIsPreparing GUARDED_BY(mMutex) = false;
+    bool mIsClosingSession GUARDED_BY(mMutex) = false;
+    std::shared_ptr<IVibrationSession> mSession GUARDED_BY(mMutex) = nullptr;
+    std::shared_ptr<IVibratorCallback> mSessionCallback GUARDED_BY(mMutex) = nullptr;
+
+    void clearSession(const std::shared_ptr<IVibrationSession>& session);
 };
 
 }  // namespace vibrator
diff --git a/vibrator/aidl/vts/VtsHalVibratorManagerTargetTest.cpp b/vibrator/aidl/vts/VtsHalVibratorManagerTargetTest.cpp
index 3c2a360..101d4f5 100644
--- a/vibrator/aidl/vts/VtsHalVibratorManagerTargetTest.cpp
+++ b/vibrator/aidl/vts/VtsHalVibratorManagerTargetTest.cpp
@@ -16,12 +16,14 @@
 #include <aidl/Gtest.h>
 #include <aidl/Vintf.h>
 #include <aidl/android/hardware/vibrator/BnVibratorCallback.h>
+#include <aidl/android/hardware/vibrator/IVibrationSession.h>
 #include <aidl/android/hardware/vibrator/IVibrator.h>
 #include <aidl/android/hardware/vibrator/IVibratorManager.h>
 
 #include <android/binder_manager.h>
 #include <android/binder_process.h>
 
+#include <algorithm>
 #include <cmath>
 #include <future>
 
@@ -32,10 +34,14 @@
 using aidl::android::hardware::vibrator::CompositePrimitive;
 using aidl::android::hardware::vibrator::Effect;
 using aidl::android::hardware::vibrator::EffectStrength;
+using aidl::android::hardware::vibrator::IVibrationSession;
 using aidl::android::hardware::vibrator::IVibrator;
 using aidl::android::hardware::vibrator::IVibratorManager;
+using aidl::android::hardware::vibrator::VibrationSessionConfig;
 using std::chrono::high_resolution_clock;
 
+using namespace ::std::chrono_literals;
+
 const std::vector<Effect> kEffects{ndk::enum_range<Effect>().begin(),
                                    ndk::enum_range<Effect>().end()};
 const std::vector<EffectStrength> kEffectStrengths{ndk::enum_range<EffectStrength>().begin(),
@@ -43,6 +49,11 @@
 const std::vector<CompositePrimitive> kPrimitives{ndk::enum_range<CompositePrimitive>().begin(),
                                                   ndk::enum_range<CompositePrimitive>().end()};
 
+// Timeout to wait for vibration callback completion.
+static constexpr std::chrono::milliseconds VIBRATION_CALLBACK_TIMEOUT = 100ms;
+
+static constexpr int32_t VIBRATION_SESSIONS_MIN_VERSION = 3;
+
 class CompletionCallback : public BnVibratorCallback {
   public:
     CompletionCallback(const std::function<void()>& callback) : mCallback(callback) {}
@@ -64,9 +75,29 @@
         ASSERT_NE(manager, nullptr);
         EXPECT_OK(manager->getCapabilities(&capabilities));
         EXPECT_OK(manager->getVibratorIds(&vibratorIds));
+        EXPECT_OK(manager->getInterfaceVersion(&version));
+    }
+
+    virtual void TearDown() override {
+        // Reset manager state between tests.
+        if (capabilities & IVibratorManager::CAP_SYNC) {
+            manager->cancelSynced();
+        }
+        if (capabilities & IVibratorManager::CAP_START_SESSIONS) {
+            manager->clearSessions();
+        }
+        // Reset all managed vibrators.
+        for (int32_t id : vibratorIds) {
+            std::shared_ptr<IVibrator> vibrator;
+            EXPECT_OK(manager->getVibrator(id, &vibrator));
+            ASSERT_NE(vibrator, nullptr);
+            EXPECT_OK(vibrator->off());
+        }
     }
 
     std::shared_ptr<IVibratorManager> manager;
+    std::shared_ptr<IVibrationSession> session;
+    int32_t version;
     int32_t capabilities;
     std::vector<int32_t> vibratorIds;
 };
@@ -109,7 +140,7 @@
     if (vibratorIds.empty()) return;
     if (!(capabilities & IVibratorManager::CAP_SYNC)) return;
     if (!(capabilities & IVibratorManager::CAP_PREPARE_ON)) {
-        uint32_t durationMs = 250;
+        int32_t durationMs = 250;
         EXPECT_OK(manager->prepareSynced(vibratorIds));
         std::shared_ptr<IVibrator> vibrator;
         for (int32_t id : vibratorIds) {
@@ -170,7 +201,7 @@
     std::future<void> completionFuture{completionPromise.get_future()};
     auto callback = ndk::SharedRefBase::make<CompletionCallback>(
             [&completionPromise] { completionPromise.set_value(); });
-    uint32_t durationMs = 250;
+    int32_t durationMs = 250;
     std::chrono::milliseconds timeout{durationMs * 2};
 
     EXPECT_OK(manager->prepareSynced(vibratorIds));
@@ -202,6 +233,435 @@
     }
 }
 
+TEST_P(VibratorAidl, VibrationSessionsSupported) {
+    if (!(capabilities & IVibratorManager::CAP_START_SESSIONS)) return;
+    if (vibratorIds.empty()) return;
+
+    std::promise<void> sessionPromise;
+    std::future<void> sessionFuture{sessionPromise.get_future()};
+    auto sessionCallback = ndk::SharedRefBase::make<CompletionCallback>(
+            [&sessionPromise] { sessionPromise.set_value(); });
+
+    VibrationSessionConfig sessionConfig;
+    EXPECT_OK(manager->startSession(vibratorIds, sessionConfig, sessionCallback, &session));
+    ASSERT_NE(session, nullptr);
+
+    int32_t durationMs = 250;
+    std::vector<std::promise<void>> vibrationPromises;
+    std::vector<std::future<void>> vibrationFutures;
+    for (int32_t id : vibratorIds) {
+        std::shared_ptr<IVibrator> vibrator;
+        EXPECT_OK(manager->getVibrator(id, &vibrator));
+        ASSERT_NE(vibrator, nullptr);
+
+        std::promise<void>& vibrationPromise = vibrationPromises.emplace_back();
+        vibrationFutures.push_back(vibrationPromise.get_future());
+        auto vibrationCallback = ndk::SharedRefBase::make<CompletionCallback>(
+                [&vibrationPromise] { vibrationPromise.set_value(); });
+        EXPECT_OK(vibrator->on(durationMs, vibrationCallback));
+    }
+
+    auto timeout = std::chrono::milliseconds(durationMs) + VIBRATION_CALLBACK_TIMEOUT;
+    for (std::future<void>& future : vibrationFutures) {
+        EXPECT_EQ(future.wait_for(timeout), std::future_status::ready);
+    }
+
+    // Session callback not triggered.
+    EXPECT_EQ(sessionFuture.wait_for(VIBRATION_CALLBACK_TIMEOUT), std::future_status::timeout);
+
+    // Ending a session should not take long since the vibration was already completed
+    EXPECT_OK(session->close());
+    EXPECT_EQ(sessionFuture.wait_for(VIBRATION_CALLBACK_TIMEOUT), std::future_status::ready);
+}
+
+TEST_P(VibratorAidl, VibrationSessionInterrupted) {
+    if (!(capabilities & IVibratorManager::CAP_START_SESSIONS)) return;
+    if (vibratorIds.empty()) return;
+
+    std::promise<void> sessionPromise;
+    std::future<void> sessionFuture{sessionPromise.get_future()};
+    auto sessionCallback = ndk::SharedRefBase::make<CompletionCallback>(
+            [&sessionPromise] { sessionPromise.set_value(); });
+
+    VibrationSessionConfig sessionConfig;
+    EXPECT_OK(manager->startSession(vibratorIds, sessionConfig, sessionCallback, &session));
+    ASSERT_NE(session, nullptr);
+
+    std::vector<std::promise<void>> vibrationPromises;
+    std::vector<std::future<void>> vibrationFutures;
+    for (int32_t id : vibratorIds) {
+        std::shared_ptr<IVibrator> vibrator;
+        EXPECT_OK(manager->getVibrator(id, &vibrator));
+        ASSERT_NE(vibrator, nullptr);
+
+        std::promise<void>& vibrationPromise = vibrationPromises.emplace_back();
+        vibrationFutures.push_back(vibrationPromise.get_future());
+        auto vibrationCallback = ndk::SharedRefBase::make<CompletionCallback>(
+                [&vibrationPromise] { vibrationPromise.set_value(); });
+
+        // Vibration longer than test timeout.
+        EXPECT_OK(vibrator->on(2000, vibrationCallback));
+    }
+
+    // Session callback not triggered.
+    EXPECT_EQ(sessionFuture.wait_for(VIBRATION_CALLBACK_TIMEOUT), std::future_status::timeout);
+
+    // Interrupt vibrations and session.
+    EXPECT_OK(session->abort());
+
+    // Both callbacks triggered.
+    EXPECT_EQ(sessionFuture.wait_for(VIBRATION_CALLBACK_TIMEOUT), std::future_status::ready);
+    for (std::future<void>& future : vibrationFutures) {
+        EXPECT_EQ(future.wait_for(VIBRATION_CALLBACK_TIMEOUT), std::future_status::ready);
+    }
+}
+
+TEST_P(VibratorAidl, VibrationSessionEndingInterrupted) {
+    if (!(capabilities & IVibratorManager::CAP_START_SESSIONS)) return;
+    if (vibratorIds.empty()) return;
+
+    std::promise<void> sessionPromise;
+    std::future<void> sessionFuture{sessionPromise.get_future()};
+    auto sessionCallback = ndk::SharedRefBase::make<CompletionCallback>(
+            [&sessionPromise] { sessionPromise.set_value(); });
+
+    VibrationSessionConfig sessionConfig;
+    EXPECT_OK(manager->startSession(vibratorIds, sessionConfig, sessionCallback, &session));
+    ASSERT_NE(session, nullptr);
+
+    std::vector<std::promise<void>> vibrationPromises;
+    std::vector<std::future<void>> vibrationFutures;
+    for (int32_t id : vibratorIds) {
+        std::shared_ptr<IVibrator> vibrator;
+        EXPECT_OK(manager->getVibrator(id, &vibrator));
+        ASSERT_NE(vibrator, nullptr);
+
+        std::promise<void>& vibrationPromise = vibrationPromises.emplace_back();
+        vibrationFutures.push_back(vibrationPromise.get_future());
+        auto vibrationCallback = ndk::SharedRefBase::make<CompletionCallback>(
+                [&vibrationPromise] { vibrationPromise.set_value(); });
+
+        // Vibration longer than test timeout.
+        EXPECT_OK(vibrator->on(2000, vibrationCallback));
+    }
+
+    // Session callback not triggered.
+    EXPECT_EQ(sessionFuture.wait_for(VIBRATION_CALLBACK_TIMEOUT), std::future_status::timeout);
+
+    // End session, this might take a while
+    EXPECT_OK(session->close());
+
+    // Interrupt ending session.
+    EXPECT_OK(session->abort());
+
+    // Both callbacks triggered.
+    EXPECT_EQ(sessionFuture.wait_for(VIBRATION_CALLBACK_TIMEOUT), std::future_status::ready);
+    for (std::future<void>& future : vibrationFutures) {
+        EXPECT_EQ(future.wait_for(VIBRATION_CALLBACK_TIMEOUT), std::future_status::ready);
+    }
+}
+
+TEST_P(VibratorAidl, VibrationSessionCleared) {
+    if (!(capabilities & IVibratorManager::CAP_START_SESSIONS)) return;
+    if (vibratorIds.empty()) return;
+
+    std::promise<void> sessionPromise;
+    std::future<void> sessionFuture{sessionPromise.get_future()};
+    auto sessionCallback = ndk::SharedRefBase::make<CompletionCallback>(
+            [&sessionPromise] { sessionPromise.set_value(); });
+
+    VibrationSessionConfig sessionConfig;
+    EXPECT_OK(manager->startSession(vibratorIds, sessionConfig, sessionCallback, &session));
+    ASSERT_NE(session, nullptr);
+
+    int32_t durationMs = 250;
+    std::vector<std::promise<void>> vibrationPromises;
+    std::vector<std::future<void>> vibrationFutures;
+    for (int32_t id : vibratorIds) {
+        std::shared_ptr<IVibrator> vibrator;
+        EXPECT_OK(manager->getVibrator(id, &vibrator));
+        ASSERT_NE(vibrator, nullptr);
+
+        std::promise<void>& vibrationPromise = vibrationPromises.emplace_back();
+        vibrationFutures.push_back(vibrationPromise.get_future());
+        auto vibrationCallback = ndk::SharedRefBase::make<CompletionCallback>(
+                [&vibrationPromise] { vibrationPromise.set_value(); });
+        EXPECT_OK(vibrator->on(durationMs, vibrationCallback));
+    }
+
+    // Session callback not triggered.
+    EXPECT_EQ(sessionFuture.wait_for(VIBRATION_CALLBACK_TIMEOUT), std::future_status::timeout);
+
+    // Clearing sessions should abort ongoing session
+    EXPECT_OK(manager->clearSessions());
+
+    EXPECT_EQ(sessionFuture.wait_for(VIBRATION_CALLBACK_TIMEOUT), std::future_status::ready);
+    for (std::future<void>& future : vibrationFutures) {
+        EXPECT_EQ(future.wait_for(VIBRATION_CALLBACK_TIMEOUT), std::future_status::ready);
+    }
+}
+
+TEST_P(VibratorAidl, VibrationSessionsClearedWithoutSession) {
+    if (!(capabilities & IVibratorManager::CAP_START_SESSIONS)) return;
+
+    EXPECT_OK(manager->clearSessions());
+}
+
+TEST_P(VibratorAidl, VibrationSessionsWithSyncedVibrations) {
+    if (!(capabilities & IVibratorManager::CAP_START_SESSIONS)) return;
+    if (!(capabilities & IVibratorManager::CAP_SYNC)) return;
+    if (!(capabilities & IVibratorManager::CAP_PREPARE_ON)) return;
+    if (!(capabilities & IVibratorManager::CAP_TRIGGER_CALLBACK)) return;
+    if (vibratorIds.empty()) return;
+
+    std::promise<void> sessionPromise;
+    std::future<void> sessionFuture{sessionPromise.get_future()};
+    auto sessionCallback = ndk::SharedRefBase::make<CompletionCallback>(
+            [&sessionPromise] { sessionPromise.set_value(); });
+
+    VibrationSessionConfig sessionConfig;
+    EXPECT_OK(manager->startSession(vibratorIds, sessionConfig, sessionCallback, &session));
+    ASSERT_NE(session, nullptr);
+
+    EXPECT_OK(manager->prepareSynced(vibratorIds));
+
+    int32_t durationMs = 250;
+    std::vector<std::promise<void>> vibrationPromises;
+    std::vector<std::future<void>> vibrationFutures;
+    for (int32_t id : vibratorIds) {
+        std::shared_ptr<IVibrator> vibrator;
+        EXPECT_OK(manager->getVibrator(id, &vibrator));
+        ASSERT_NE(vibrator, nullptr);
+
+        std::promise<void>& vibrationPromise = vibrationPromises.emplace_back();
+        vibrationFutures.push_back(vibrationPromise.get_future());
+        auto vibrationCallback = ndk::SharedRefBase::make<CompletionCallback>(
+                [&vibrationPromise] { vibrationPromise.set_value(); });
+        EXPECT_OK(vibrator->on(durationMs, vibrationCallback));
+    }
+
+    std::promise<void> triggerPromise;
+    std::future<void> triggerFuture{triggerPromise.get_future()};
+    auto triggerCallback = ndk::SharedRefBase::make<CompletionCallback>(
+            [&triggerPromise] { triggerPromise.set_value(); });
+
+    EXPECT_OK(manager->triggerSynced(triggerCallback));
+
+    auto timeout = std::chrono::milliseconds(durationMs) + VIBRATION_CALLBACK_TIMEOUT;
+    EXPECT_EQ(triggerFuture.wait_for(timeout), std::future_status::ready);
+    for (std::future<void>& future : vibrationFutures) {
+        EXPECT_EQ(future.wait_for(timeout), std::future_status::ready);
+    }
+
+    // Session callback not triggered.
+    EXPECT_EQ(sessionFuture.wait_for(VIBRATION_CALLBACK_TIMEOUT), std::future_status::timeout);
+
+    // Ending a session should not take long since the vibration was already completed
+    EXPECT_OK(session->close());
+    EXPECT_EQ(sessionFuture.wait_for(VIBRATION_CALLBACK_TIMEOUT), std::future_status::ready);
+}
+
+TEST_P(VibratorAidl, VibrationSessionWithMultipleIndependentVibrations) {
+    if (!(capabilities & IVibratorManager::CAP_START_SESSIONS)) return;
+    if (vibratorIds.empty()) return;
+
+    std::promise<void> sessionPromise;
+    std::future<void> sessionFuture{sessionPromise.get_future()};
+    auto sessionCallback = ndk::SharedRefBase::make<CompletionCallback>(
+            [&sessionPromise] { sessionPromise.set_value(); });
+
+    VibrationSessionConfig sessionConfig;
+    EXPECT_OK(manager->startSession(vibratorIds, sessionConfig, sessionCallback, &session));
+    ASSERT_NE(session, nullptr);
+
+    for (int32_t id : vibratorIds) {
+        std::shared_ptr<IVibrator> vibrator;
+        EXPECT_OK(manager->getVibrator(id, &vibrator));
+        ASSERT_NE(vibrator, nullptr);
+
+        EXPECT_OK(vibrator->on(100, nullptr));
+        EXPECT_OK(vibrator->on(200, nullptr));
+        EXPECT_OK(vibrator->on(300, nullptr));
+    }
+
+    // Session callback not triggered.
+    EXPECT_EQ(sessionFuture.wait_for(VIBRATION_CALLBACK_TIMEOUT), std::future_status::timeout);
+
+    EXPECT_OK(session->close());
+
+    int32_t maxDurationMs = 100 + 200 + 300;
+    auto timeout = std::chrono::milliseconds(maxDurationMs) + VIBRATION_CALLBACK_TIMEOUT;
+    EXPECT_EQ(sessionFuture.wait_for(timeout), std::future_status::ready);
+}
+
+TEST_P(VibratorAidl, VibrationSessionsIgnoresSecondSessionWhenFirstIsOngoing) {
+    if (!(capabilities & IVibratorManager::CAP_START_SESSIONS)) return;
+    if (vibratorIds.empty()) return;
+
+    std::promise<void> sessionPromise;
+    std::future<void> sessionFuture{sessionPromise.get_future()};
+    auto sessionCallback = ndk::SharedRefBase::make<CompletionCallback>(
+            [&sessionPromise] { sessionPromise.set_value(); });
+
+    VibrationSessionConfig sessionConfig;
+    EXPECT_OK(manager->startSession(vibratorIds, sessionConfig, sessionCallback, &session));
+    ASSERT_NE(session, nullptr);
+
+    std::shared_ptr<IVibrationSession> secondSession;
+    EXPECT_ILLEGAL_STATE(
+            manager->startSession(vibratorIds, sessionConfig, nullptr, &secondSession));
+    EXPECT_EQ(secondSession, nullptr);
+
+    // First session was not cancelled.
+    EXPECT_EQ(sessionFuture.wait_for(VIBRATION_CALLBACK_TIMEOUT), std::future_status::timeout);
+
+    // First session still ongoing, we can still vibrate.
+    int32_t durationMs = 100;
+    for (int32_t id : vibratorIds) {
+        std::shared_ptr<IVibrator> vibrator;
+        EXPECT_OK(manager->getVibrator(id, &vibrator));
+        ASSERT_NE(vibrator, nullptr);
+        EXPECT_OK(vibrator->on(durationMs, nullptr));
+    }
+
+    EXPECT_OK(session->close());
+
+    auto timeout = std::chrono::milliseconds(durationMs) + VIBRATION_CALLBACK_TIMEOUT;
+    EXPECT_EQ(sessionFuture.wait_for(timeout), std::future_status::ready);
+}
+
+TEST_P(VibratorAidl, VibrationSessionEndMultipleTimes) {
+    if (!(capabilities & IVibratorManager::CAP_START_SESSIONS)) return;
+    if (vibratorIds.empty()) return;
+
+    std::promise<void> sessionPromise;
+    std::future<void> sessionFuture{sessionPromise.get_future()};
+    auto sessionCallback = ndk::SharedRefBase::make<CompletionCallback>(
+            [&sessionPromise] { sessionPromise.set_value(); });
+
+    VibrationSessionConfig sessionConfig;
+    EXPECT_OK(manager->startSession(vibratorIds, sessionConfig, sessionCallback, &session));
+    ASSERT_NE(session, nullptr);
+
+    int32_t durationMs = 250;
+    std::vector<std::promise<void>> vibrationPromises;
+    std::vector<std::future<void>> vibrationFutures;
+    for (int32_t id : vibratorIds) {
+        std::shared_ptr<IVibrator> vibrator;
+        EXPECT_OK(manager->getVibrator(id, &vibrator));
+        ASSERT_NE(vibrator, nullptr);
+
+        std::promise<void>& vibrationPromise = vibrationPromises.emplace_back();
+        vibrationFutures.push_back(vibrationPromise.get_future());
+        auto vibrationCallback = ndk::SharedRefBase::make<CompletionCallback>(
+                [&vibrationPromise] { vibrationPromise.set_value(); });
+        EXPECT_OK(vibrator->on(durationMs, vibrationCallback));
+    }
+
+    // Session callback not triggered.
+    EXPECT_EQ(sessionFuture.wait_for(VIBRATION_CALLBACK_TIMEOUT), std::future_status::timeout);
+
+    // End session, this might take a while
+    EXPECT_OK(session->close());
+
+    // End session again
+    EXPECT_OK(session->close());
+
+    // Both callbacks triggered within timeout.
+    auto timeout = std::chrono::milliseconds(durationMs) + VIBRATION_CALLBACK_TIMEOUT;
+    EXPECT_EQ(sessionFuture.wait_for(timeout), std::future_status::ready);
+    for (std::future<void>& future : vibrationFutures) {
+        EXPECT_EQ(future.wait_for(timeout), std::future_status::ready);
+    }
+}
+
+TEST_P(VibratorAidl, VibrationSessionDeletedAfterEnded) {
+    if (!(capabilities & IVibratorManager::CAP_START_SESSIONS)) return;
+    if (vibratorIds.empty()) return;
+
+    std::promise<void> sessionPromise;
+    std::future<void> sessionFuture{sessionPromise.get_future()};
+    auto sessionCallback = ndk::SharedRefBase::make<CompletionCallback>(
+            [&sessionPromise] { sessionPromise.set_value(); });
+
+    VibrationSessionConfig sessionConfig;
+    EXPECT_OK(manager->startSession(vibratorIds, sessionConfig, sessionCallback, &session));
+    ASSERT_NE(session, nullptr);
+
+    int32_t durationMs = 250;
+    std::vector<std::promise<void>> vibrationPromises;
+    std::vector<std::future<void>> vibrationFutures;
+    for (int32_t id : vibratorIds) {
+        std::shared_ptr<IVibrator> vibrator;
+        EXPECT_OK(manager->getVibrator(id, &vibrator));
+        ASSERT_NE(vibrator, nullptr);
+
+        std::promise<void>& vibrationPromise = vibrationPromises.emplace_back();
+        vibrationFutures.push_back(vibrationPromise.get_future());
+        auto vibrationCallback = ndk::SharedRefBase::make<CompletionCallback>(
+                [&vibrationPromise] { vibrationPromise.set_value(); });
+        EXPECT_OK(vibrator->on(durationMs, vibrationCallback));
+    }
+
+    // Session callback not triggered.
+    EXPECT_EQ(sessionFuture.wait_for(VIBRATION_CALLBACK_TIMEOUT), std::future_status::timeout);
+
+    // End session, this might take a while
+    EXPECT_OK(session->close());
+
+    session.reset();
+
+    // Both callbacks triggered within timeout, even after session was deleted.
+    auto timeout = std::chrono::milliseconds(durationMs) + VIBRATION_CALLBACK_TIMEOUT;
+    EXPECT_EQ(sessionFuture.wait_for(timeout), std::future_status::ready);
+    for (std::future<void>& future : vibrationFutures) {
+        EXPECT_EQ(future.wait_for(timeout), std::future_status::ready);
+    }
+}
+
+TEST_P(VibratorAidl, VibrationSessionWrongVibratorIdsFail) {
+    if (!(capabilities & IVibratorManager::CAP_START_SESSIONS)) return;
+
+    auto maxIdIt = std::max_element(vibratorIds.begin(), vibratorIds.end());
+    int32_t wrongId = maxIdIt == vibratorIds.end() ? 0 : *maxIdIt + 1;
+
+    std::vector<int32_t> emptyIds;
+    std::vector<int32_t> wrongIds{wrongId};
+    VibrationSessionConfig sessionConfig;
+    EXPECT_ILLEGAL_ARGUMENT(manager->startSession(emptyIds, sessionConfig, nullptr, &session));
+    EXPECT_ILLEGAL_ARGUMENT(manager->startSession(wrongIds, sessionConfig, nullptr, &session));
+    EXPECT_EQ(session, nullptr);
+}
+
+TEST_P(VibratorAidl, VibrationSessionDuringPrepareSyncedFails) {
+    if (!(capabilities & IVibratorManager::CAP_SYNC)) return;
+    if (!(capabilities & IVibratorManager::CAP_START_SESSIONS)) return;
+    if (vibratorIds.empty()) return;
+
+    EXPECT_OK(manager->prepareSynced(vibratorIds));
+
+    VibrationSessionConfig sessionConfig;
+    EXPECT_ILLEGAL_STATE(manager->startSession(vibratorIds, sessionConfig, nullptr, &session));
+    EXPECT_EQ(session, nullptr);
+
+    EXPECT_OK(manager->cancelSynced());
+}
+
+TEST_P(VibratorAidl, VibrationSessionsUnsupported) {
+    if (version < VIBRATION_SESSIONS_MIN_VERSION) {
+        EXPECT_EQ(capabilities & IVibratorManager::CAP_START_SESSIONS, 0)
+                << "Vibrator manager version " << version
+                << " should not report start session capability";
+    }
+    if (capabilities & IVibratorManager::CAP_START_SESSIONS) return;
+
+    VibrationSessionConfig sessionConfig;
+    EXPECT_UNKNOWN_OR_UNSUPPORTED(
+            manager->startSession(vibratorIds, sessionConfig, nullptr, &session));
+    EXPECT_EQ(session, nullptr);
+    EXPECT_UNKNOWN_OR_UNSUPPORTED(manager->clearSessions());
+}
+
 std::vector<std::string> FindVibratorManagerNames() {
     std::vector<std::string> names;
     constexpr auto callback = [](const char* instance, void* context) {
@@ -219,7 +679,7 @@
 
 int main(int argc, char** argv) {
     ::testing::InitGoogleTest(&argc, argv);
-    ABinderProcess_setThreadPoolMaxThreadCount(1);
+    ABinderProcess_setThreadPoolMaxThreadCount(2);
     ABinderProcess_startThreadPool();
     return RUN_ALL_TESTS();
 }
diff --git a/vibrator/aidl/vts/test_utils.h b/vibrator/aidl/vts/test_utils.h
index aaf3211..e884bbd 100644
--- a/vibrator/aidl/vts/test_utils.h
+++ b/vibrator/aidl/vts/test_utils.h
@@ -57,4 +57,17 @@
 #error Macro EXPECT_ILLEGAL_ARGUMENT already defined unexpectedly
 #endif
 
+#if !defined(EXPECT_ILLEGAL_STATE)
+#define EXPECT_ILLEGAL_STATE(expression)                                  \
+    GTEST_AMBIGUOUS_ELSE_BLOCKER_                                         \
+    if (const ::ndk::ScopedAStatus&& _status = (expression);              \
+        _status.getExceptionCode() == EX_ILLEGAL_STATE)                   \
+        ;                                                                 \
+    else                                                                  \
+        ADD_FAILURE() << "Expected EX_ILLEGAL_STATE for: " << #expression \
+                      << "\n  Actual: " << _status
+#else
+#error Macro EXPECT_ILLEGAL_STATE already defined unexpectedly
+#endif
+
 #endif  // VIBRATOR_HAL_TEST_UTILS_H