Introduce IVibrator.performVendorEffect

Introduce HAL API to perform vibration effects defined by the vendor.

The new API accepts a PersistableBundle representing the effect and some
platform-provided parameters, e.g. EffectStrength and scale.

The callback support for completion is required for this API, since
effect duration is undefined.

Fix: 345409060
Test: VtsHalVibratorTargetTest
Flag: EXEMPT HAL interface change
Change-Id: I26379ede4b64e41e1fc85feae6de5105cb636511
diff --git a/vibrator/aidl/Android.bp b/vibrator/aidl/Android.bp
index fe76450..b2d98f5 100644
--- a/vibrator/aidl/Android.bp
+++ b/vibrator/aidl/Android.bp
@@ -15,6 +15,9 @@
     srcs: [
         "android/hardware/vibrator/*.aidl",
     ],
+    headers: [
+        "PersistableBundle_aidl",
+    ],
     stability: "vintf",
     backend: {
         java: {
@@ -26,9 +29,19 @@
         cpp: {
             enabled: false,
         },
+        rust: {
+            enabled: false,
+        },
     },
-    versions: [
-        "1",
-        "2",
+    versions_with_info: [
+        {
+            version: "1",
+            imports: [],
+        },
+        {
+            version: "2",
+            imports: [],
+        },
     ],
+    frozen: false,
 }
diff --git a/vibrator/aidl/TEST_MAPPING b/vibrator/aidl/TEST_MAPPING
index 2414b84..e00b3e9 100644
--- a/vibrator/aidl/TEST_MAPPING
+++ b/vibrator/aidl/TEST_MAPPING
@@ -6,5 +6,10 @@
     {
       "name": "VtsHalVibratorManagerTargetTest"
     }
+  ],
+  "postsubmit": [
+      {
+        "name": "VtsHalVibratorPerformVendorEffectFuzzer"
+      }
   ]
 }
diff --git a/vibrator/aidl/aidl_api/android.hardware.vibrator/current/android/hardware/vibrator/IVibrator.aidl b/vibrator/aidl/aidl_api/android.hardware.vibrator/current/android/hardware/vibrator/IVibrator.aidl
index b7afb66..af619c6 100644
--- a/vibrator/aidl/aidl_api/android.hardware.vibrator/current/android/hardware/vibrator/IVibrator.aidl
+++ b/vibrator/aidl/aidl_api/android.hardware.vibrator/current/android/hardware/vibrator/IVibrator.aidl
@@ -58,15 +58,17 @@
   int getPwleCompositionSizeMax();
   android.hardware.vibrator.Braking[] getSupportedBraking();
   void composePwle(in android.hardware.vibrator.PrimitivePwle[] composite, in android.hardware.vibrator.IVibratorCallback callback);
-  const int CAP_ON_CALLBACK = 1;
-  const int CAP_PERFORM_CALLBACK = 2;
-  const int CAP_AMPLITUDE_CONTROL = 4;
-  const int CAP_EXTERNAL_CONTROL = 8;
-  const int CAP_EXTERNAL_AMPLITUDE_CONTROL = 16;
-  const int CAP_COMPOSE_EFFECTS = 32;
-  const int CAP_ALWAYS_ON_CONTROL = 64;
-  const int CAP_GET_RESONANT_FREQUENCY = 128;
-  const int CAP_GET_Q_FACTOR = 256;
-  const int CAP_FREQUENCY_CONTROL = 512;
-  const int CAP_COMPOSE_PWLE_EFFECTS = 1024;
+  void performVendorEffect(in android.hardware.vibrator.VendorEffect vendorEffect, in android.hardware.vibrator.IVibratorCallback callback);
+  const int CAP_ON_CALLBACK = (1 << 0) /* 1 */;
+  const int CAP_PERFORM_CALLBACK = (1 << 1) /* 2 */;
+  const int CAP_AMPLITUDE_CONTROL = (1 << 2) /* 4 */;
+  const int CAP_EXTERNAL_CONTROL = (1 << 3) /* 8 */;
+  const int CAP_EXTERNAL_AMPLITUDE_CONTROL = (1 << 4) /* 16 */;
+  const int CAP_COMPOSE_EFFECTS = (1 << 5) /* 32 */;
+  const int CAP_ALWAYS_ON_CONTROL = (1 << 6) /* 64 */;
+  const int CAP_GET_RESONANT_FREQUENCY = (1 << 7) /* 128 */;
+  const int CAP_GET_Q_FACTOR = (1 << 8) /* 256 */;
+  const int CAP_FREQUENCY_CONTROL = (1 << 9) /* 512 */;
+  const int CAP_COMPOSE_PWLE_EFFECTS = (1 << 10) /* 1024 */;
+  const int CAP_PERFORM_VENDOR_EFFECTS = (1 << 11) /* 2048 */;
 }
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 290c68d..ef5794c 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,12 +40,12 @@
   void prepareSynced(in int[] vibratorIds);
   void triggerSynced(in android.hardware.vibrator.IVibratorCallback callback);
   void cancelSynced();
-  const int CAP_SYNC = 1;
-  const int CAP_PREPARE_ON = 2;
-  const int CAP_PREPARE_PERFORM = 4;
-  const int CAP_PREPARE_COMPOSE = 8;
-  const int CAP_MIXED_TRIGGER_ON = 16;
-  const int CAP_MIXED_TRIGGER_PERFORM = 32;
-  const int CAP_MIXED_TRIGGER_COMPOSE = 64;
-  const int CAP_TRIGGER_CALLBACK = 128;
+  const int CAP_SYNC = (1 << 0) /* 1 */;
+  const int CAP_PREPARE_ON = (1 << 1) /* 2 */;
+  const int CAP_PREPARE_PERFORM = (1 << 2) /* 4 */;
+  const int CAP_PREPARE_COMPOSE = (1 << 3) /* 8 */;
+  const int CAP_MIXED_TRIGGER_ON = (1 << 4) /* 16 */;
+  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 */;
 }
diff --git a/vibrator/aidl/aidl_api/android.hardware.vibrator/current/android/hardware/vibrator/VendorEffect.aidl b/vibrator/aidl/aidl_api/android.hardware.vibrator/current/android/hardware/vibrator/VendorEffect.aidl
new file mode 100644
index 0000000..1900084
--- /dev/null
+++ b/vibrator/aidl/aidl_api/android.hardware.vibrator/current/android/hardware/vibrator/VendorEffect.aidl
@@ -0,0 +1,40 @@
+/*
+ * 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 VendorEffect {
+  android.os.PersistableBundle vendorData;
+  android.hardware.vibrator.EffectStrength strength = android.hardware.vibrator.EffectStrength.MEDIUM;
+  float scale;
+}
diff --git a/vibrator/aidl/android/hardware/vibrator/Braking.aidl b/vibrator/aidl/android/hardware/vibrator/Braking.aidl
index 2bc51db..f934ff2 100644
--- a/vibrator/aidl/android/hardware/vibrator/Braking.aidl
+++ b/vibrator/aidl/android/hardware/vibrator/Braking.aidl
@@ -23,12 +23,12 @@
      * No braking mechanism used.
      * This is the default if the hardware does not support any braking mechanism.
      */
-    NONE,
+    NONE = 0,
     /**
      * Closed-loop active braking.
      *
      * This effect should produce a sharp, crisp end to the waveform
      * Support is optional.
      */
-    CLAB,
+    CLAB = 1,
 }
diff --git a/vibrator/aidl/android/hardware/vibrator/CompositePrimitive.aidl b/vibrator/aidl/android/hardware/vibrator/CompositePrimitive.aidl
index 5314898..5f8ee8d 100644
--- a/vibrator/aidl/android/hardware/vibrator/CompositePrimitive.aidl
+++ b/vibrator/aidl/android/hardware/vibrator/CompositePrimitive.aidl
@@ -24,13 +24,13 @@
      *
      * Support is required.
      */
-    NOOP,
+    NOOP = 0,
     /**
      * This effect should produce a sharp, crisp click sensation.
      *
      * Support is required.
      */
-    CLICK,
+    CLICK = 1,
     /**
      * A haptic effect that simulates downwards movement with gravity. Often
      * followed by extra energy of hitting and reverberation to augment
@@ -38,43 +38,43 @@
      *
      * Support is optional.
      */
-    THUD,
+    THUD = 2,
     /**
      * A haptic effect that simulates spinning momentum.
      *
      * Support is optional.
      */
-    SPIN,
+    SPIN = 3,
     /**
      * A haptic effect that simulates quick upward movement against gravity.
      *
      * Support is required.
      */
-    QUICK_RISE,
+    QUICK_RISE = 4,
     /**
      * A haptic effect that simulates slow upward movement against gravity.
      *
      * Support is required.
      */
-    SLOW_RISE,
+    SLOW_RISE = 5,
     /**
      * A haptic effect that simulates quick downwards movement with gravity.
      *
      * Support is required.
      */
-    QUICK_FALL,
+    QUICK_FALL = 6,
     /**
      * This very short effect should produce a light crisp sensation intended
      * to be used repetitively for dynamic feedback.
      *
      * Support is required.
      */
-    LIGHT_TICK,
+    LIGHT_TICK = 7,
     /**
      * This very short low frequency effect should produce a light crisp sensation intended
      * to be used repetitively for dynamic feedback.
      *
      * Support is required.
      */
-    LOW_TICK,
+    LOW_TICK = 8,
 }
diff --git a/vibrator/aidl/android/hardware/vibrator/Effect.aidl b/vibrator/aidl/android/hardware/vibrator/Effect.aidl
index c60bfe9..f5cf9e3 100644
--- a/vibrator/aidl/android/hardware/vibrator/Effect.aidl
+++ b/vibrator/aidl/android/hardware/vibrator/Effect.aidl
@@ -24,57 +24,57 @@
      *
      * This effect should produce a sharp, crisp click sensation.
      */
-    CLICK,
+    CLICK = 0,
     /**
      * A double click effect.
      *
      * This effect should produce two sequential sharp, crisp click sensations with a minimal
      * amount of time between them.
      */
-    DOUBLE_CLICK,
+    DOUBLE_CLICK = 1,
     /**
      * A tick effect.
      *
      * This effect should produce a soft, short sensation, like the tick of a clock.
      */
-    TICK,
+    TICK = 2,
     /**
      * A thud effect.
      *
      * This effect should solid feeling bump, like the depression of a heavy mechanical button.
      */
-    THUD,
+    THUD = 3,
     /**
      * A pop effect.
      *
      * A short, quick burst effect.
      */
-    POP,
+    POP = 4,
     /**
      * A heavy click effect.
      *
      * This should produce a sharp striking sensation, like a click but stronger.
      */
-    HEAVY_CLICK,
+    HEAVY_CLICK = 5,
     /**
      * Ringtone patterns. They may correspond with the device's ringtone audio, or may just be a
      * pattern that can be played as a ringtone with any audio, depending on the device.
      */
-    RINGTONE_1,
-    RINGTONE_2,
-    RINGTONE_3,
-    RINGTONE_4,
-    RINGTONE_5,
-    RINGTONE_6,
-    RINGTONE_7,
-    RINGTONE_8,
-    RINGTONE_9,
-    RINGTONE_10,
-    RINGTONE_11,
-    RINGTONE_12,
-    RINGTONE_13,
-    RINGTONE_14,
-    RINGTONE_15,
+    RINGTONE_1 = 6,
+    RINGTONE_2 = 7,
+    RINGTONE_3 = 8,
+    RINGTONE_4 = 9,
+    RINGTONE_5 = 10,
+    RINGTONE_6 = 11,
+    RINGTONE_7 = 12,
+    RINGTONE_8 = 13,
+    RINGTONE_9 = 14,
+    RINGTONE_10 = 15,
+    RINGTONE_11 = 16,
+    RINGTONE_12 = 17,
+    RINGTONE_13 = 18,
+    RINGTONE_14 = 19,
+    RINGTONE_15 = 20,
     /**
      * A soft tick effect meant to be played as a texture.
      *
@@ -82,5 +82,5 @@
      * are expected to be played multiple times in quick succession, replicating a specific
      * texture to the user as a form of haptic feedback.
      */
-    TEXTURE_TICK,
+    TEXTURE_TICK = 21,
 }
diff --git a/vibrator/aidl/android/hardware/vibrator/EffectStrength.aidl b/vibrator/aidl/android/hardware/vibrator/EffectStrength.aidl
index 66f70e5..c6a78d4 100644
--- a/vibrator/aidl/android/hardware/vibrator/EffectStrength.aidl
+++ b/vibrator/aidl/android/hardware/vibrator/EffectStrength.aidl
@@ -19,7 +19,7 @@
 @VintfStability
 @Backing(type="byte")
 enum EffectStrength {
-    LIGHT,
-    MEDIUM,
-    STRONG,
+    LIGHT = 0,
+    MEDIUM = 1,
+    STRONG = 2,
 }
diff --git a/vibrator/aidl/android/hardware/vibrator/IVibrator.aidl b/vibrator/aidl/android/hardware/vibrator/IVibrator.aidl
index b4e7e44..768ec4f 100644
--- a/vibrator/aidl/android/hardware/vibrator/IVibrator.aidl
+++ b/vibrator/aidl/android/hardware/vibrator/IVibrator.aidl
@@ -16,13 +16,14 @@
 
 package android.hardware.vibrator;
 
-import android.hardware.vibrator.IVibratorCallback;
 import android.hardware.vibrator.Braking;
-import android.hardware.vibrator.Effect;
-import android.hardware.vibrator.EffectStrength;
 import android.hardware.vibrator.CompositeEffect;
 import android.hardware.vibrator.CompositePrimitive;
+import android.hardware.vibrator.Effect;
+import android.hardware.vibrator.EffectStrength;
+import android.hardware.vibrator.IVibratorCallback;
 import android.hardware.vibrator.PrimitivePwle;
+import android.hardware.vibrator.VendorEffect;
 
 @VintfStability
 interface IVibrator {
@@ -70,6 +71,10 @@
      * Whether composePwle is supported.
      */
     const int CAP_COMPOSE_PWLE_EFFECTS = 1 << 10;
+    /**
+     * Whether perform w/ vendor effect is supported.
+     */
+    const int CAP_PERFORM_VENDOR_EFFECTS = 1 << 11;
 
     /**
      * Determine capabilities of the vibrator HAL (CAP_* mask)
@@ -359,4 +364,25 @@
      * @param composite Array of PWLEs.
      */
     void composePwle(in PrimitivePwle[] composite, in IVibratorCallback callback);
+
+    /**
+     * Fire off a vendor-defined haptic event.
+     *
+     * This may not be supported and this support is reflected in
+     * getCapabilities (CAP_PERFORM_VENDOR_EFFECTS).
+     *
+     * The duration of the effect is unknown and can be undefined for looping effects.
+     * IVibratorCallback.onComplete() support is required for this API.
+     *
+     * Doing this operation while the vibrator is already on is undefined behavior. Clients should
+     * explicitly call off.
+     *
+     * @param effect The vendor data representing the effect to be performed.
+     * @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 bad framework parameters, e.g. scale or effect strength.
+     *         - EX_SERVICE_SPECIFIC for bad vendor data, vibration is not triggered.
+     */
+    void performVendorEffect(in VendorEffect vendorEffect, in IVibratorCallback callback);
 }
diff --git a/vibrator/aidl/android/hardware/vibrator/VendorEffect.aidl b/vibrator/aidl/android/hardware/vibrator/VendorEffect.aidl
new file mode 100644
index 0000000..2155aca
--- /dev/null
+++ b/vibrator/aidl/android/hardware/vibrator/VendorEffect.aidl
@@ -0,0 +1,51 @@
+/*
+ * 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;
+
+import android.hardware.vibrator.EffectStrength;
+import android.os.PersistableBundle;
+
+@VintfStability
+parcelable VendorEffect {
+    /**
+     * Vendor data describing the haptic effect. Expected fields should be defined by the vendor.
+     *
+     * Vendors can use this as a platform extension point for experimental hardware capabilities,
+     * but they are strongly discouraged from using it as an alternative to the AOSP support for
+     * stable vibrator APIs. Implemenitng vendor-specific custom effects outside the platform APIs
+     * will hinder portability for the code and overall user experience.
+     *
+     * Vendors are encouraged to upstream new capabilities to the IVibrator surface once it has
+     * matured into a stable interface.
+     */
+    PersistableBundle vendorData;
+
+    /**
+     * The intensity of the haptic effect.
+     */
+    EffectStrength strength = EffectStrength.MEDIUM;
+
+    /**
+     * A scale to be applied to the haptic effect intensity.
+     *
+     * This value represents a linear scale that should be applied on top of the effect strength to
+     * dynamically adapt to the device state.
+     *
+     * Values in [0,1) should scale down. Values > 1 should scale up within hardware bounds.
+     */
+    float scale;
+}
diff --git a/vibrator/aidl/default/Android.bp b/vibrator/aidl/default/Android.bp
index 0f342db..4b26640 100644
--- a/vibrator/aidl/default/Android.bp
+++ b/vibrator/aidl/default/Android.bp
@@ -15,7 +15,7 @@
     shared_libs: [
         "libbase",
         "libbinder_ndk",
-        "android.hardware.vibrator-V2-ndk",
+        "android.hardware.vibrator-V3-ndk",
     ],
     export_include_dirs: ["include"],
     srcs: [
@@ -49,7 +49,7 @@
     shared_libs: [
         "libbase",
         "libbinder_ndk",
-        "android.hardware.vibrator-V2-ndk",
+        "android.hardware.vibrator-V3-ndk",
     ],
     static_libs: [
         "libvibratorexampleimpl",
@@ -62,7 +62,7 @@
     host_supported: true,
     defaults: ["service_fuzzer_defaults"],
     static_libs: [
-        "android.hardware.vibrator-V2-ndk",
+        "android.hardware.vibrator-V3-ndk",
         "liblog",
         "libvibratorexampleimpl",
     ],
diff --git a/vibrator/aidl/default/Vibrator.cpp b/vibrator/aidl/default/Vibrator.cpp
index 01602ab..acf7d34 100644
--- a/vibrator/aidl/default/Vibrator.cpp
+++ b/vibrator/aidl/default/Vibrator.cpp
@@ -39,6 +39,9 @@
 static constexpr float PWLE_BW_MAP_SIZE =
         1 + ((PWLE_FREQUENCY_MAX_HZ - PWLE_FREQUENCY_MIN_HZ) / PWLE_FREQUENCY_RESOLUTION_HZ);
 
+// Service specific error code used for vendor vibration effects.
+static constexpr int32_t ERROR_CODE_INVALID_DURATION = 1;
+
 ndk::ScopedAStatus Vibrator::getCapabilities(int32_t* _aidl_return) {
     LOG(VERBOSE) << "Vibrator reporting capabilities";
     *_aidl_return = IVibrator::CAP_ON_CALLBACK | IVibrator::CAP_PERFORM_CALLBACK |
@@ -46,7 +49,7 @@
                     IVibrator::CAP_EXTERNAL_AMPLITUDE_CONTROL | IVibrator::CAP_COMPOSE_EFFECTS |
                     IVibrator::CAP_ALWAYS_ON_CONTROL | IVibrator::CAP_GET_RESONANT_FREQUENCY |
                     IVibrator::CAP_GET_Q_FACTOR | IVibrator::CAP_FREQUENCY_CONTROL |
-                    IVibrator::CAP_COMPOSE_PWLE_EFFECTS;
+                    IVibrator::CAP_COMPOSE_PWLE_EFFECTS | IVibrator::CAP_PERFORM_VENDOR_EFFECTS;
     return ndk::ScopedAStatus::ok();
 }
 
@@ -102,6 +105,36 @@
     return ndk::ScopedAStatus::ok();
 }
 
+ndk::ScopedAStatus Vibrator::performVendorEffect(
+        const VendorEffect& effect, const std::shared_ptr<IVibratorCallback>& callback) {
+    LOG(VERBOSE) << "Vibrator perform vendor effect";
+    EffectStrength strength = effect.strength;
+    if (strength != EffectStrength::LIGHT && strength != EffectStrength::MEDIUM &&
+        strength != EffectStrength::STRONG) {
+        return ndk::ScopedAStatus(AStatus_fromExceptionCode(EX_ILLEGAL_ARGUMENT));
+    }
+    float scale = effect.scale;
+    if (scale <= 0) {
+        return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
+    }
+
+    int32_t durationMs = 0;
+    if (!effect.vendorData.getInt("DURATION_MS", &durationMs) || durationMs <= 0) {
+        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();
+    }
+
+    return ndk::ScopedAStatus::ok();
+}
+
 ndk::ScopedAStatus Vibrator::getSupportedEffects(std::vector<Effect>* _aidl_return) {
     *_aidl_return = {Effect::CLICK, Effect::TICK};
     return ndk::ScopedAStatus::ok();
diff --git a/vibrator/aidl/default/android.hardware.vibrator.xml b/vibrator/aidl/default/android.hardware.vibrator.xml
index b5bd3dd..b730046 100644
--- a/vibrator/aidl/default/android.hardware.vibrator.xml
+++ b/vibrator/aidl/default/android.hardware.vibrator.xml
@@ -1,12 +1,12 @@
 <manifest version="1.0" type="device">
     <hal format="aidl">
         <name>android.hardware.vibrator</name>
-        <version>2</version>
+        <version>3</version>
         <fqname>IVibrator/default</fqname>
     </hal>
     <hal format="aidl">
         <name>android.hardware.vibrator</name>
-        <version>2</version>
+        <version>3</version>
         <fqname>IVibratorManager/default</fqname>
     </hal>
 </manifest>
diff --git a/vibrator/aidl/default/include/vibrator-impl/Vibrator.h b/vibrator/aidl/default/include/vibrator-impl/Vibrator.h
index 4203bf2..e8f64ca 100644
--- a/vibrator/aidl/default/include/vibrator-impl/Vibrator.h
+++ b/vibrator/aidl/default/include/vibrator-impl/Vibrator.h
@@ -31,6 +31,9 @@
     ndk::ScopedAStatus perform(Effect effect, EffectStrength strength,
                                const std::shared_ptr<IVibratorCallback>& callback,
                                int32_t* _aidl_return) override;
+    ndk::ScopedAStatus performVendorEffect(
+            const VendorEffect& effect,
+            const std::shared_ptr<IVibratorCallback>& callback) override;
     ndk::ScopedAStatus getSupportedEffects(std::vector<Effect>* _aidl_return) override;
     ndk::ScopedAStatus setAmplitude(float amplitude) override;
     ndk::ScopedAStatus setExternalControl(bool enabled) override;
diff --git a/vibrator/aidl/vts/Android.bp b/vibrator/aidl/vts/Android.bp
index 166b30b..a48bb2e 100644
--- a/vibrator/aidl/vts/Android.bp
+++ b/vibrator/aidl/vts/Android.bp
@@ -20,7 +20,7 @@
         "libbinder_ndk",
     ],
     static_libs: [
-        "android.hardware.vibrator-V2-ndk",
+        "android.hardware.vibrator-V3-ndk",
     ],
     test_suites: [
         "general-tests",
@@ -39,7 +39,7 @@
         "libbinder_ndk",
     ],
     static_libs: [
-        "android.hardware.vibrator-V2-ndk",
+        "android.hardware.vibrator-V3-ndk",
     ],
     test_suites: [
         "general-tests",
diff --git a/vibrator/aidl/vts/VtsHalVibratorTargetTest.cpp b/vibrator/aidl/vts/VtsHalVibratorTargetTest.cpp
index 65a1e84..706ab41 100644
--- a/vibrator/aidl/vts/VtsHalVibratorTargetTest.cpp
+++ b/vibrator/aidl/vts/VtsHalVibratorTargetTest.cpp
@@ -21,10 +21,14 @@
 
 #include <android/binder_manager.h>
 #include <android/binder_process.h>
+#include <android/persistable_bundle_aidl.h>
 
 #include <cmath>
+#include <cstdlib>
+#include <ctime>
 #include <future>
 
+#include "persistable_bundle_utils.h"
 #include "test_utils.h"
 
 using aidl::android::hardware::vibrator::ActivePwle;
@@ -38,6 +42,8 @@
 using aidl::android::hardware::vibrator::IVibrator;
 using aidl::android::hardware::vibrator::IVibratorManager;
 using aidl::android::hardware::vibrator::PrimitivePwle;
+using aidl::android::hardware::vibrator::VendorEffect;
+using aidl::android::os::PersistableBundle;
 using std::chrono::high_resolution_clock;
 
 using namespace ::std::chrono_literals;
@@ -357,6 +363,122 @@
     }
 }
 
+TEST_P(VibratorAidl, PerformVendorEffectSupported) {
+    if ((capabilities & IVibrator::CAP_PERFORM_VENDOR_EFFECTS) == 0) return;
+
+    float scale = 0.5f;
+    for (EffectStrength strength : kEffectStrengths) {
+        PersistableBundle vendorData;
+        ::aidl::android::hardware::vibrator::testing::fillBasicData(&vendorData);
+
+        PersistableBundle nestedData;
+        ::aidl::android::hardware::vibrator::testing::fillBasicData(&nestedData);
+        vendorData.putPersistableBundle("test_nested_bundle", nestedData);
+
+        VendorEffect effect;
+        effect.vendorData = vendorData;
+        effect.strength = strength;
+        effect.scale = scale;
+        scale *= 1.5f;
+
+        auto callback = ndk::SharedRefBase::make<CompletionCallback>([] {});
+        ndk::ScopedAStatus status = vibrator->performVendorEffect(effect, callback);
+
+        // No expectations on the actual status, the effect might be refused with illegal argument
+        // or the vendor might return a service-specific error code.
+        EXPECT_TRUE(status.getExceptionCode() != EX_UNSUPPORTED_OPERATION &&
+                    status.getStatus() != STATUS_UNKNOWN_TRANSACTION)
+                << status << "\n For vendor effect with strength" << toString(strength)
+                << " and scale " << effect.scale;
+
+        if (status.isOk()) {
+            // Generic vendor data should not trigger vibrations, but if it does trigger one
+            // then we make sure the vibrator is reset by triggering off().
+            EXPECT_OK(vibrator->off());
+        }
+    }
+}
+
+TEST_P(VibratorAidl, PerformVendorEffectStability) {
+    if ((capabilities & IVibrator::CAP_PERFORM_VENDOR_EFFECTS) == 0) return;
+
+    // Run some iterations of performVendorEffect with randomized vendor data to check basic
+    // stability of the implementation.
+    uint8_t iterations = 200;
+
+    for (EffectStrength strength : kEffectStrengths) {
+        float scale = 0.5f;
+        for (uint8_t i = 0; i < iterations; i++) {
+            PersistableBundle vendorData;
+            ::aidl::android::hardware::vibrator::testing::fillRandomData(&vendorData);
+
+            VendorEffect effect;
+            effect.vendorData = vendorData;
+            effect.strength = strength;
+            effect.scale = scale;
+            scale *= 2;
+
+            auto callback = ndk::SharedRefBase::make<CompletionCallback>([] {});
+            ndk::ScopedAStatus status = vibrator->performVendorEffect(effect, callback);
+
+            // No expectations on the actual status, the effect might be refused with illegal
+            // argument or the vendor might return a service-specific error code.
+            EXPECT_TRUE(status.getExceptionCode() != EX_UNSUPPORTED_OPERATION &&
+                        status.getStatus() != STATUS_UNKNOWN_TRANSACTION)
+                    << status << "\n For random vendor effect with strength " << toString(strength)
+                    << " and scale " << effect.scale;
+
+            if (status.isOk()) {
+                // Random vendor data should not trigger vibrations, but if it does trigger one
+                // then we make sure the vibrator is reset by triggering off().
+                EXPECT_OK(vibrator->off());
+            }
+        }
+    }
+}
+
+TEST_P(VibratorAidl, PerformVendorEffectEmptyVendorData) {
+    if ((capabilities & IVibrator::CAP_PERFORM_VENDOR_EFFECTS) == 0) return;
+
+    for (EffectStrength strength : kEffectStrengths) {
+        VendorEffect effect;
+        effect.strength = strength;
+        effect.scale = 1.0f;
+
+        ndk::ScopedAStatus status = vibrator->performVendorEffect(effect, nullptr /*callback*/);
+
+        EXPECT_TRUE(status.getExceptionCode() == EX_SERVICE_SPECIFIC)
+                << status << "\n For vendor effect with strength " << toString(strength)
+                << " and scale " << effect.scale;
+    }
+}
+
+TEST_P(VibratorAidl, PerformVendorEffectInvalidScale) {
+    if ((capabilities & IVibrator::CAP_PERFORM_VENDOR_EFFECTS) == 0) return;
+
+    VendorEffect effect;
+    effect.strength = EffectStrength::MEDIUM;
+
+    effect.scale = 0.0f;
+    EXPECT_ILLEGAL_ARGUMENT(vibrator->performVendorEffect(effect, nullptr /*callback*/));
+
+    effect.scale = -1.0f;
+    EXPECT_ILLEGAL_ARGUMENT(vibrator->performVendorEffect(effect, nullptr /*callback*/));
+}
+
+TEST_P(VibratorAidl, PerformVendorEffectUnsupported) {
+    if (capabilities & IVibrator::CAP_PERFORM_VENDOR_EFFECTS) return;
+
+    for (EffectStrength strength : kEffectStrengths) {
+        VendorEffect effect;
+        effect.strength = strength;
+        effect.scale = 1.0f;
+
+        EXPECT_UNKNOWN_OR_UNSUPPORTED(vibrator->performVendorEffect(effect, nullptr /*callback*/))
+                << "\n  For vendor effect with strength " << toString(strength);
+    }
+}
+
 TEST_P(VibratorAidl, ChangeVibrationAmplitude) {
     if (capabilities & IVibrator::CAP_AMPLITUDE_CONTROL) {
         EXPECT_OK(vibrator->setAmplitude(0.1f));
@@ -940,6 +1062,9 @@
                          PrintGeneratedTest);
 
 int main(int argc, char **argv) {
+    // Random values are used in the implementation.
+    std::srand(std::time(nullptr));
+
     ::testing::InitGoogleTest(&argc, argv);
     ABinderProcess_setThreadPoolMaxThreadCount(1);
     ABinderProcess_startThreadPool();
diff --git a/vibrator/aidl/vts/persistable_bundle_utils.h b/vibrator/aidl/vts/persistable_bundle_utils.h
new file mode 100644
index 0000000..a765a49
--- /dev/null
+++ b/vibrator/aidl/vts/persistable_bundle_utils.h
@@ -0,0 +1,134 @@
+/*
+ * 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.
+ */
+#ifndef VIBRATOR_HAL_PERSISTABLE_BUNDLE_UTILS_H
+#define VIBRATOR_HAL_PERSISTABLE_BUNDLE_UTILS_H
+
+#include <android/persistable_bundle_aidl.h>
+
+#include <cstdlib>
+
+namespace aidl {
+namespace android {
+namespace hardware {
+namespace vibrator {
+namespace testing {
+
+using aidl::android::os::PersistableBundle;
+
+namespace {
+
+template <typename T>
+T nextValue() {
+    return static_cast<T>(std::rand());
+}
+
+template <>
+std::string nextValue() {
+    std::string str;
+    uint8_t entryCount = nextValue<uint8_t>();
+    for (uint8_t i = 0; i < entryCount; i++) {
+        str.push_back(nextValue<char>());
+    }
+    return str;
+}
+
+template <typename T>
+T nextValue(T limit) {
+    assert(limit > 0);
+    return static_cast<T>(std::rand()) / (static_cast<T>(RAND_MAX / limit));
+}
+
+template <typename T>
+void fillVector(std::vector<T>* values) {
+    uint8_t entryCount = nextValue<uint8_t>();
+    for (uint8_t i = 0; i < entryCount; i++) {
+        values->push_back(nextValue<T>());
+    }
+}
+
+const std::vector<std::function<void(PersistableBundle*, const std::string&)>>
+        sPersistableBundleSetters = {[](PersistableBundle* bundle, const std::string& key) -> void {
+                                         bundle->putBoolean(key, nextValue<bool>());
+                                     },
+                                     [](PersistableBundle* bundle, const std::string& key) -> void {
+                                         bundle->putInt(key, nextValue<int32_t>());
+                                     },
+                                     [](PersistableBundle* bundle, const std::string& key) -> void {
+                                         bundle->putLong(key, nextValue<int64_t>());
+                                     },
+                                     [](PersistableBundle* bundle, const std::string& key) -> void {
+                                         bundle->putDouble(key, nextValue<double>());
+                                     },
+                                     [](PersistableBundle* bundle, const std::string& key) -> void {
+                                         bundle->putString(key, nextValue<std::string>());
+                                     },
+                                     [](PersistableBundle* bundle, const std::string& key) -> void {
+                                         std::vector<bool> value;
+                                         fillVector<bool>(&value);
+                                         bundle->putBooleanVector(key, value);
+                                     },
+                                     [](PersistableBundle* bundle, const std::string& key) -> void {
+                                         std::vector<int32_t> value;
+                                         fillVector<int32_t>(&value);
+                                         bundle->putIntVector(key, value);
+                                     },
+                                     [](PersistableBundle* bundle, const std::string& key) -> void {
+                                         std::vector<int64_t> value;
+                                         fillVector<int64_t>(&value);
+                                         bundle->putLongVector(key, value);
+                                     },
+                                     [](PersistableBundle* bundle, const std::string& key) -> void {
+                                         std::vector<double> value;
+                                         fillVector<double>(&value);
+                                         bundle->putDoubleVector(key, value);
+                                     },
+                                     [](PersistableBundle* bundle, const std::string& key) -> void {
+                                         std::vector<std::string> value;
+                                         fillVector<std::string>(&value);
+                                         bundle->putStringVector(key, value);
+                                     }};
+
+}  // namespace
+
+void fillBasicData(PersistableBundle* bundle) {
+    bundle->putBoolean("test_bool", true);
+    bundle->putInt("test_int", 2147483647);
+    bundle->putLong("test_long", 2147483647L);
+    bundle->putDouble("test_double", 1.23);
+    bundle->putString("test_string", "test data");
+    bundle->putBooleanVector("test_bool_vector", {true, false, false});
+    bundle->putIntVector("test_int_vector", {1, 2, 3, 4});
+    bundle->putLongVector("test_long_vector", {100L, 200L, 300L});
+    bundle->putDoubleVector("test_double_vector", {1.1, 2.2});
+    bundle->putStringVector("test_string_vector", {"test", "val"});
+}
+
+void fillRandomData(PersistableBundle* bundle) {
+    uint8_t entryCount = nextValue<uint8_t>();
+    for (uint8_t i = 0; i < entryCount; i++) {
+        std::string key(nextValue<std::string>());
+        uint8_t setterIdx = nextValue<uint8_t>(sPersistableBundleSetters.size() - 1);
+        sPersistableBundleSetters[setterIdx](bundle, key);
+    }
+}
+
+}  // namespace testing
+}  // namespace vibrator
+}  // namespace hardware
+}  // namespace android
+}  // namespace aidl
+
+#endif  // VIBRATOR_HAL_PERSISTABLE_BUNDLE_UTILS_H
diff --git a/vibrator/bench/Android.bp b/vibrator/bench/Android.bp
index b31c719..cd56516 100644
--- a/vibrator/bench/Android.bp
+++ b/vibrator/bench/Android.bp
@@ -30,7 +30,7 @@
         "benchmark.cpp",
     ],
     shared_libs: [
-        "android.hardware.vibrator-V2-ndk",
+        "android.hardware.vibrator-V3-ndk",
         "android.hardware.vibrator@1.0",
         "android.hardware.vibrator@1.1",
         "android.hardware.vibrator@1.2",