浏览代码

device/vibrator: Move vibrator HAL into device folder for l26

Moved L26 portion of vibrator hal from hardware/google/pixel to
device/google.

Bug: 198239103
Test: None
Change-Id: I4dc77f5770929337905878c7ee6acbbfa489bc57
Signed-off-by: Chris Paulo <[email protected]>
Chris Paulo 2 年之前
父节点
当前提交
24789fe332

+ 52 - 0
vibrator/Android.bp

@@ -0,0 +1,52 @@
+//
+// Copyright (C) 2019 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 {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+cc_defaults {
+    name: "PixelVibratorDefaults",
+    relative_install_path: "hw",
+    static_libs: [
+        "PixelVibratorCommon",
+    ],
+    shared_libs: [
+        "libbase",
+        "libbinder_ndk",
+        "libcutils",
+        "libhardware",
+        "liblog",
+        "libutils",
+    ],
+}
+
+cc_defaults {
+    name: "PixelVibratorBinaryDefaults",
+    defaults: ["PixelVibratorDefaults"],
+    shared_libs: [
+        "android.hardware.vibrator-V2-ndk",
+    ],
+}
+
+cc_defaults {
+    name: "PixelVibratorTestDefaults",
+    defaults: ["PixelVibratorDefaults"],
+    static_libs: [
+        "android.hardware.vibrator-V2-ndk",
+    ],
+    test_suites: ["device-tests"],
+    require_root: true,
+}

+ 3 - 0
vibrator/OWNERS


+ 37 - 0
vibrator/common/Android.bp

@@ -0,0 +1,37 @@
+//
+// Copyright (C) 2019 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 {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+cc_library {
+    name: "PixelVibratorCommon",
+    srcs: [
+        "HardwareBase.cpp",
+    ],
+    shared_libs: [
+        "libbase",
+        "libcutils",
+        "liblog",
+        "libutils",
+    ],
+    cflags: [
+        "-DATRACE_TAG=(ATRACE_TAG_VIBRATOR | ATRACE_TAG_HAL)",
+        "-DLOG_TAG=\"[email protected]\"",
+    ],
+    export_include_dirs: ["."],
+    vendor_available: true,
+}

+ 136 - 0
vibrator/common/HardwareBase.cpp

@@ -0,0 +1,136 @@
+/*
+ * Copyright (C) 2019 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 "HardwareBase.h"
+
+#include <cutils/properties.h>
+#include <log/log.h>
+
+#include <fstream>
+#include <sstream>
+
+#include "utils.h"
+
+namespace aidl {
+namespace android {
+namespace hardware {
+namespace vibrator {
+
+HwApiBase::HwApiBase() {
+    mPathPrefix = std::getenv("HWAPI_PATH_PREFIX") ?: "";
+    if (mPathPrefix.empty()) {
+        ALOGE("Failed get HWAPI path prefix!");
+    }
+}
+
+void HwApiBase::saveName(const std::string &name, const std::ios *stream) {
+    mNames[stream] = name;
+}
+
+bool HwApiBase::has(const std::ios &stream) {
+    return !!stream;
+}
+
+void HwApiBase::debug(int fd) {
+    dprintf(fd, "Kernel:\n");
+
+    for (auto &entry : utils::pathsFromEnv("HWAPI_DEBUG_PATHS", mPathPrefix)) {
+        auto &path = entry.first;
+        auto &stream = entry.second;
+        std::string line;
+
+        dprintf(fd, "  %s:\n", path.c_str());
+        while (std::getline(stream, line)) {
+            dprintf(fd, "    %s\n", line.c_str());
+        }
+    }
+
+    mRecordsMutex.lock();
+    dprintf(fd, "  Records:\n");
+    for (auto &r : mRecords) {
+        if (r == nullptr) {
+            continue;
+        }
+        dprintf(fd, "    %s\n", r->toString(mNames).c_str());
+    }
+    mRecordsMutex.unlock();
+}
+
+HwCalBase::HwCalBase() {
+    std::ifstream calfile;
+    auto propertyPrefix = std::getenv("PROPERTY_PREFIX");
+
+    if (propertyPrefix != NULL) {
+        mPropertyPrefix = std::string(propertyPrefix);
+    } else {
+        ALOGE("Failed get property prefix!");
+    }
+
+    utils::fileFromEnv("CALIBRATION_FILEPATH", &calfile);
+
+    for (std::string line; std::getline(calfile, line);) {
+        if (line.empty() || line[0] == '#') {
+            continue;
+        }
+        std::istringstream is_line(line);
+        std::string key, value;
+        if (std::getline(is_line, key, ':') && std::getline(is_line, value)) {
+            mCalData[utils::trim(key)] = utils::trim(value);
+        }
+    }
+}
+
+void HwCalBase::debug(int fd) {
+    std::ifstream stream;
+    std::string path;
+    std::string line;
+    struct context {
+        HwCalBase *obj;
+        int fd;
+    } context{this, fd};
+
+    dprintf(fd, "Properties:\n");
+
+    property_list(
+            [](const char *key, const char *value, void *cookie) {
+                struct context *context = static_cast<struct context *>(cookie);
+                HwCalBase *obj = context->obj;
+                int fd = context->fd;
+                const std::string expect{obj->mPropertyPrefix};
+                const std::string actual{key, std::min(strlen(key), expect.size())};
+                if (actual == expect) {
+                    dprintf(fd, "  %s:\n", key);
+                    dprintf(fd, "    %s\n", value);
+                }
+            },
+            &context);
+
+    dprintf(fd, "\n");
+
+    dprintf(fd, "Persist:\n");
+
+    utils::fileFromEnv("CALIBRATION_FILEPATH", &stream, &path);
+
+    dprintf(fd, "  %s:\n", path.c_str());
+    while (std::getline(stream, line)) {
+        dprintf(fd, "    %s\n", line.c_str());
+    }
+}
+
+}  // namespace vibrator
+}  // namespace hardware
+}  // namespace android
+}  // namespace aidl

+ 221 - 0
vibrator/common/HardwareBase.h

@@ -0,0 +1,221 @@
+/*
+ * Copyright (C) 2019 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 <list>
+#include <map>
+#include <sstream>
+#include <string>
+
+#include <android-base/unique_fd.h>
+#include <log/log.h>
+#include <sys/epoll.h>
+#include <utils/Trace.h>
+
+#include "utils.h"
+
+namespace aidl {
+namespace android {
+namespace hardware {
+namespace vibrator {
+
+using ::android::base::unique_fd;
+
+class HwApiBase {
+  private:
+    using NamesMap = std::map<const std::ios *, std::string>;
+
+    class RecordInterface {
+      public:
+        virtual std::string toString(const NamesMap &names) = 0;
+        virtual ~RecordInterface() {}
+    };
+    template <typename T>
+    class Record : public RecordInterface {
+      public:
+        Record(const char *func, const T &value, const std::ios *stream)
+            : mFunc(func), mValue(value), mStream(stream) {}
+        std::string toString(const NamesMap &names) override;
+
+      private:
+        const char *mFunc;
+        const T mValue;
+        const std::ios *mStream;
+    };
+    using Records = std::list<std::unique_ptr<RecordInterface>>;
+
+    static constexpr uint32_t RECORDS_SIZE = 32;
+
+  public:
+    HwApiBase();
+    void debug(int fd);
+
+  protected:
+    void saveName(const std::string &name, const std::ios *stream);
+    template <typename T>
+    void open(const std::string &name, T *stream);
+    bool has(const std::ios &stream);
+    template <typename T>
+    bool get(T *value, std::istream *stream);
+    template <typename T>
+    bool set(const T &value, std::ostream *stream);
+    template <typename T>
+    bool poll(const T &value, std::istream *stream, const int32_t timeout = -1);
+    template <typename T>
+    void record(const char *func, const T &value, const std::ios *stream);
+
+  private:
+    std::string mPathPrefix;
+    NamesMap mNames;
+    Records mRecords{RECORDS_SIZE};
+    std::mutex mRecordsMutex;
+    std::mutex mIoMutex;
+};
+
+#define HWAPI_RECORD(args...) HwApiBase::record(__FUNCTION__, ##args)
+
+template <typename T>
+void HwApiBase::open(const std::string &name, T *stream) {
+    saveName(name, stream);
+    utils::openNoCreate(mPathPrefix + name, stream);
+}
+
+template <typename T>
+bool HwApiBase::get(T *value, std::istream *stream) {
+    ATRACE_NAME("HwApi::get");
+    std::scoped_lock ioLock{mIoMutex};
+    bool ret;
+    stream->seekg(0);
+    *stream >> *value;
+    if (!(ret = !!*stream)) {
+        ALOGE("Failed to read %s (%d): %s", mNames[stream].c_str(), errno, strerror(errno));
+    }
+    stream->clear();
+    HWAPI_RECORD(*value, stream);
+    return ret;
+}
+
+template <typename T>
+bool HwApiBase::set(const T &value, std::ostream *stream) {
+    ATRACE_NAME("HwApi::set");
+    using utils::operator<<;
+    std::scoped_lock ioLock{mIoMutex};
+    bool ret;
+    *stream << value << std::endl;
+    if (!(ret = !!*stream)) {
+        ALOGE("Failed to write %s (%d): %s", mNames[stream].c_str(), errno, strerror(errno));
+        stream->clear();
+    }
+    HWAPI_RECORD(value, stream);
+    return ret;
+}
+
+template <typename T>
+bool HwApiBase::poll(const T &value, std::istream *stream, const int32_t timeoutMs) {
+    ATRACE_NAME("HwApi::poll");
+    auto path = mPathPrefix + mNames[stream];
+    unique_fd fileFd{::open(path.c_str(), O_RDONLY)};
+    unique_fd epollFd{epoll_create(1)};
+    epoll_event event = {
+            .events = EPOLLPRI | EPOLLET,
+    };
+    T actual;
+    bool ret;
+    int epollRet;
+
+    if (timeoutMs < -1) {
+        ALOGE("Invalid polling timeout!");
+        return false;
+    }
+
+    if (epoll_ctl(epollFd, EPOLL_CTL_ADD, fileFd, &event)) {
+        ALOGE("Failed to poll %s (%d): %s", mNames[stream].c_str(), errno, strerror(errno));
+        return false;
+    }
+
+    while ((ret = get(&actual, stream)) && (actual != value)) {
+        epollRet = epoll_wait(epollFd, &event, 1, timeoutMs);
+        if (epollRet <= 0) {
+            ALOGE("Polling error or timeout! (%d)", epollRet);
+            return false;
+        }
+    }
+
+    HWAPI_RECORD(value, stream);
+    return ret;
+}
+
+template <typename T>
+void HwApiBase::record(const char *func, const T &value, const std::ios *stream) {
+    std::lock_guard<std::mutex> lock(mRecordsMutex);
+    mRecords.emplace_back(std::make_unique<Record<T>>(func, value, stream));
+    mRecords.pop_front();
+}
+
+template <typename T>
+std::string HwApiBase::Record<T>::toString(const NamesMap &names) {
+    using utils::operator<<;
+    std::stringstream ret;
+
+    ret << mFunc << " '" << names.at(mStream) << "' = '" << mValue << "'";
+
+    return ret.str();
+}
+
+class HwCalBase {
+  public:
+    HwCalBase();
+    void debug(int fd);
+
+  protected:
+    template <typename T>
+    bool getProperty(const char *key, T *value, const T defval);
+    template <typename T>
+    bool getPersist(const char *key, T *value);
+
+  private:
+    std::string mPropertyPrefix;
+    std::map<std::string, std::string> mCalData;
+};
+
+template <typename T>
+bool HwCalBase::getProperty(const char *key, T *outval, const T defval) {
+    ATRACE_NAME("HwCal::getProperty");
+    *outval = utils::getProperty(mPropertyPrefix + key, defval);
+    return true;
+}
+
+template <typename T>
+bool HwCalBase::getPersist(const char *key, T *value) {
+    ATRACE_NAME("HwCal::getPersist");
+    auto it = mCalData.find(key);
+    if (it == mCalData.end()) {
+        ALOGE("Missing %s config!", key);
+        return false;
+    }
+    std::stringstream stream{it->second};
+    utils::unpack(stream, value);
+    if (!stream || !stream.eof()) {
+        ALOGE("Invalid %s config!", key);
+        return false;
+    }
+    return true;
+}
+
+}  // namespace vibrator
+}  // namespace hardware
+}  // namespace android
+}  // namespace aidl

+ 173 - 0
vibrator/common/utils.h

@@ -0,0 +1,173 @@
+/*
+ * Copyright (C) 2019 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 <android-base/macros.h>
+#include <android-base/parsedouble.h>
+#include <android-base/properties.h>
+#include <log/log.h>
+
+#include <fstream>
+#include <map>
+#include <sstream>
+
+namespace aidl {
+namespace android {
+namespace hardware {
+namespace vibrator {
+namespace utils {
+
+template <typename T>
+class Is_Iterable {
+  private:
+    template <typename U>
+    static std::true_type test(typename U::iterator *u);
+
+    template <typename U>
+    static std::false_type test(U *u);
+
+  public:
+    static const bool value = decltype(test<T>(0))::value;
+};
+
+template <typename T, bool B>
+using Enable_If_Iterable = std::enable_if_t<Is_Iterable<T>::value == B>;
+
+template <typename T, typename U = void>
+using Enable_If_Signed = std::enable_if_t<std::is_signed_v<T>, U>;
+
+template <typename T, typename U = void>
+using Enable_If_Unsigned = std::enable_if_t<std::is_unsigned_v<T>, U>;
+
+// override for default behavior of printing as a character
+inline std::ostream &operator<<(std::ostream &stream, const int8_t value) {
+    return stream << +value;
+}
+// override for default behavior of printing as a character
+inline std::ostream &operator<<(std::ostream &stream, const uint8_t value) {
+    return stream << +value;
+}
+
+template <typename T>
+inline auto toUnderlying(const T value) {
+    return static_cast<std::underlying_type_t<T>>(value);
+}
+
+template <typename T>
+inline Enable_If_Iterable<T, true> unpack(std::istream &stream, T *value) {
+    for (auto &entry : *value) {
+        stream >> entry;
+    }
+}
+
+template <typename T>
+inline Enable_If_Iterable<T, false> unpack(std::istream &stream, T *value) {
+    stream >> *value;
+}
+
+template <>
+inline void unpack<std::string>(std::istream &stream, std::string *value) {
+    *value = std::string(std::istreambuf_iterator(stream), {});
+    stream.setstate(std::istream::eofbit);
+}
+
+template <typename T>
+inline Enable_If_Signed<T, T> getProperty(const std::string &key, const T def) {
+    if (std::is_floating_point_v<T>) {
+        float result;
+        std::string value = ::android::base::GetProperty(key, "");
+        if (!value.empty() && ::android::base::ParseFloat(value, &result)) {
+            return result;
+        }
+        return def;
+    } else {
+        return ::android::base::GetIntProperty(key, def);
+    }
+}
+
+template <typename T>
+inline Enable_If_Unsigned<T, T> getProperty(const std::string &key, const T def) {
+    return ::android::base::GetUintProperty(key, def);
+}
+
+template <>
+inline bool getProperty<bool>(const std::string &key, const bool def) {
+    return ::android::base::GetBoolProperty(key, def);
+}
+
+template <typename T>
+static void openNoCreate(const std::string &file, T *outStream) {
+    auto mode = std::is_base_of_v<std::ostream, T> ? std::ios_base::out : std::ios_base::in;
+
+    // Force 'in' mode to prevent file creation
+    outStream->open(file, mode | std::ios_base::in);
+    if (!*outStream) {
+        ALOGE("Failed to open %s (%d): %s", file.c_str(), errno, strerror(errno));
+    }
+}
+
+template <typename T>
+static void fileFromEnv(const char *env, T *outStream, std::string *outName = nullptr) {
+    auto file = std::getenv(env);
+
+    if (file == nullptr) {
+        ALOGE("Failed get env %s", env);
+        return;
+    }
+
+    if (outName != nullptr) {
+        *outName = std::string(file);
+    }
+
+    openNoCreate(file, outStream);
+}
+
+static ATTRIBUTE_UNUSED auto pathsFromEnv(const char *env, const std::string &prefix = "") {
+    std::map<std::string, std::ifstream> ret;
+    auto value = std::getenv(env);
+
+    if (value == nullptr) {
+        return ret;
+    }
+
+    std::istringstream paths{value};
+    std::string path;
+
+    while (paths >> path) {
+        ret[path].open(prefix + path);
+    }
+
+    return ret;
+}
+
+static ATTRIBUTE_UNUSED std::string trim(const std::string &str,
+                                         const std::string &whitespace = " \t") {
+    const auto str_begin = str.find_first_not_of(whitespace);
+    if (str_begin == std::string::npos) {
+        return "";
+    }
+
+    const auto str_end = str.find_last_not_of(whitespace);
+    const auto str_range = str_end - str_begin + 1;
+
+    return str.substr(str_begin, str_range);
+}
+
+}  // namespace utils
+}  // namespace vibrator
+}  // namespace hardware
+}  // namespace android
+}  // namespace aidl

+ 86 - 0
vibrator/cs40l26/Android.bp

@@ -0,0 +1,86 @@
+//
+// Copyright (C) 2021 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 {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+cc_defaults {
+    name: "android.hardware.vibrator-defaults.cs40l26",
+    cflags: [
+        "-DATRACE_TAG=(ATRACE_TAG_VIBRATOR | ATRACE_TAG_HAL)",
+        "-DLOG_TAG=\"android.hardware.vibrator-cs40l26\"",
+    ],
+    shared_libs: [
+        "libbinder",
+    ],
+}
+
+cc_defaults {
+    name: "VibratorHalCs40l26BinaryDefaults",
+    defaults: [
+        "PixelVibratorBinaryDefaults",
+        "android.hardware.vibrator-defaults.cs40l26",
+    ],
+    include_dirs: [
+        "external/tinyalsa/include",
+    ],
+    shared_libs: [
+        "libcutils",
+        "libtinyalsa",
+    ],
+}
+
+cc_defaults {
+    name: "VibratorHalCs40l26TestDefaults",
+    defaults: [
+        "PixelVibratorTestDefaults",
+        "android.hardware.vibrator-defaults.cs40l26",
+    ],
+    static_libs: [
+        "android.hardware.vibrator-impl.cs40l26",
+        "libtinyalsa",
+    ],
+}
+
+cc_library {
+    name: "android.hardware.vibrator-impl.cs40l26",
+    defaults: ["VibratorHalCs40l26BinaryDefaults"],
+    srcs: ["Vibrator.cpp"],
+    export_include_dirs: ["."],
+    vendor_available: true,
+    visibility: [":__subpackages__"],
+}
+
+cc_binary {
+    name: "android.hardware.vibrator-service.cs40l26",
+    defaults: ["VibratorHalCs40l26BinaryDefaults"],
+    init_rc: ["android.hardware.vibrator-service.cs40l26.rc"],
+    vintf_fragments: ["android.hardware.vibrator-service.cs40l26.xml"],
+    srcs: ["service.cpp"],
+    shared_libs: ["android.hardware.vibrator-impl.cs40l26"],
+    proprietary: true,
+}
+
+cc_binary {
+    name: "android.hardware.vibrator-service.cs40l26-dual",
+    defaults: ["VibratorHalCs40l26BinaryDefaults"],
+    init_rc: ["android.hardware.vibrator-service.cs40l26-dual.rc"],
+    vintf_fragments: ["android.hardware.vibrator-service.cs40l26-dual.xml"],
+    srcs: ["service.cpp"],
+    shared_libs: ["android.hardware.vibrator-impl.cs40l26"],
+    cflags: ["-DVIBRATOR_NAME=\"dual\""],
+    proprietary: true,
+}

+ 346 - 0
vibrator/cs40l26/Hardware.h

@@ -0,0 +1,346 @@
+/*
+ * Copyright (C) 2021 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 <algorithm>
+
+#include "HardwareBase.h"
+#include "Vibrator.h"
+
+#define PROC_SND_PCM "/proc/asound/pcm"
+#define HAPTIC_PCM_DEVICE_SYMBOL "haptic nohost playback"
+
+static struct pcm_config haptic_nohost_config = {
+        .channels = 1,
+        .rate = 48000,
+        .period_size = 80,
+        .period_count = 2,
+        .format = PCM_FORMAT_S16_LE,
+};
+
+enum WaveformIndex : uint16_t {
+    /* Physical waveform */
+    WAVEFORM_LONG_VIBRATION_EFFECT_INDEX = 0,
+    WAVEFORM_RESERVED_INDEX_1 = 1,
+    WAVEFORM_CLICK_INDEX = 2,
+    WAVEFORM_SHORT_VIBRATION_EFFECT_INDEX = 3,
+    WAVEFORM_THUD_INDEX = 4,
+    WAVEFORM_SPIN_INDEX = 5,
+    WAVEFORM_QUICK_RISE_INDEX = 6,
+    WAVEFORM_SLOW_RISE_INDEX = 7,
+    WAVEFORM_QUICK_FALL_INDEX = 8,
+    WAVEFORM_LIGHT_TICK_INDEX = 9,
+    WAVEFORM_LOW_TICK_INDEX = 10,
+    WAVEFORM_RESERVED_MFG_1,
+    WAVEFORM_RESERVED_MFG_2,
+    WAVEFORM_RESERVED_MFG_3,
+    WAVEFORM_MAX_PHYSICAL_INDEX,
+    /* OWT waveform */
+    WAVEFORM_COMPOSE = WAVEFORM_MAX_PHYSICAL_INDEX,
+    WAVEFORM_PWLE,
+    /*
+     * Refer to <linux/input.h>, the WAVEFORM_MAX_INDEX must not exceed 96.
+     * #define FF_GAIN          0x60  // 96 in decimal
+     * #define FF_MAX_EFFECTS   FF_GAIN
+     */
+    WAVEFORM_MAX_INDEX,
+};
+
+namespace aidl {
+namespace android {
+namespace hardware {
+namespace vibrator {
+
+class HwApi : public Vibrator::HwApi, private HwApiBase {
+  public:
+    HwApi() {
+        open("calibration/f0_stored", &mF0);
+        open("default/f0_offset", &mF0Offset);
+        open("calibration/redc_stored", &mRedc);
+        open("calibration/q_stored", &mQ);
+        open("default/vibe_state", &mVibeState);
+        open("default/num_waves", &mEffectCount);
+        open("default/owt_free_space", &mOwtFreeSpace);
+        open("default/f0_comp_enable", &mF0CompEnable);
+        open("default/redc_comp_enable", &mRedcCompEnable);
+        open("default/delay_before_stop_playback_us", &mMinOnOffInterval);
+    }
+
+    bool setF0(std::string value) override { return set(value, &mF0); }
+    bool setF0Offset(uint32_t value) override { return set(value, &mF0Offset); }
+    bool setRedc(std::string value) override { return set(value, &mRedc); }
+    bool setQ(std::string value) override { return set(value, &mQ); }
+    bool getEffectCount(uint32_t *value) override { return get(value, &mEffectCount); }
+    bool pollVibeState(uint32_t value, int32_t timeoutMs) override {
+        return poll(value, &mVibeState, timeoutMs);
+    }
+    bool hasOwtFreeSpace() override { return has(mOwtFreeSpace); }
+    bool getOwtFreeSpace(uint32_t *value) override { return get(value, &mOwtFreeSpace); }
+    bool setF0CompEnable(bool value) override { return set(value, &mF0CompEnable); }
+    bool setRedcCompEnable(bool value) override { return set(value, &mRedcCompEnable); }
+    bool setMinOnOffInterval(uint32_t value) override { return set(value, &mMinOnOffInterval); }
+    // TODO(b/234338136): Need to add the force feedback HW API test cases
+    bool setFFGain(int fd, uint16_t value) override {
+        struct input_event gain = {
+                .type = EV_FF,
+                .code = FF_GAIN,
+                .value = value,
+        };
+        if (write(fd, (const void *)&gain, sizeof(gain)) != sizeof(gain)) {
+            return false;
+        }
+        return true;
+    }
+    bool setFFEffect(int fd, struct ff_effect *effect, uint16_t timeoutMs) override {
+        if (((*effect).replay.length != timeoutMs) || (ioctl(fd, EVIOCSFF, effect) < 0)) {
+            ALOGE("setFFEffect fail");
+            return false;
+        } else {
+            return true;
+        }
+    }
+    bool setFFPlay(int fd, int8_t index, bool value) override {
+        struct input_event play = {
+                .type = EV_FF,
+                .code = static_cast<uint16_t>(index),
+                .value = value,
+        };
+        if (write(fd, (const void *)&play, sizeof(play)) != sizeof(play)) {
+            return false;
+        } else {
+            return true;
+        }
+    }
+    bool getHapticAlsaDevice(int *card, int *device) override {
+        std::string line;
+        std::ifstream myfile(PROC_SND_PCM);
+        if (myfile.is_open()) {
+            while (getline(myfile, line)) {
+                if (line.find(HAPTIC_PCM_DEVICE_SYMBOL) != std::string::npos) {
+                    std::stringstream ss(line);
+                    std::string currentToken;
+                    std::getline(ss, currentToken, ':');
+                    sscanf(currentToken.c_str(), "%d-%d", card, device);
+                    return true;
+                }
+            }
+            myfile.close();
+        } else {
+            ALOGE("Failed to read file: %s", PROC_SND_PCM);
+        }
+        return false;
+    }
+    bool setHapticPcmAmp(struct pcm **haptic_pcm, bool enable, int card, int device) override {
+        int ret = 0;
+
+        if (enable) {
+            *haptic_pcm = pcm_open(card, device, PCM_OUT, &haptic_nohost_config);
+            if (!pcm_is_ready(*haptic_pcm)) {
+                ALOGE("cannot open pcm_out driver: %s", pcm_get_error(*haptic_pcm));
+                goto fail;
+            }
+
+            ret = pcm_prepare(*haptic_pcm);
+            if (ret < 0) {
+                ALOGE("cannot prepare haptic_pcm: %s", pcm_get_error(*haptic_pcm));
+                goto fail;
+            }
+
+            ret = pcm_start(*haptic_pcm);
+            if (ret < 0) {
+                ALOGE("cannot start haptic_pcm: %s", pcm_get_error(*haptic_pcm));
+                goto fail;
+            }
+
+            return true;
+        } else {
+            if (*haptic_pcm) {
+                pcm_close(*haptic_pcm);
+                *haptic_pcm = NULL;
+            }
+            return true;
+        }
+
+    fail:
+        pcm_close(*haptic_pcm);
+        *haptic_pcm = NULL;
+        return false;
+    }
+    bool uploadOwtEffect(int fd, uint8_t *owtData, uint32_t numBytes, struct ff_effect *effect,
+                         uint32_t *outEffectIndex, int *status) override {
+        (*effect).u.periodic.custom_len = numBytes / sizeof(uint16_t);
+        delete[] ((*effect).u.periodic.custom_data);
+        (*effect).u.periodic.custom_data = new int16_t[(*effect).u.periodic.custom_len]{0x0000};
+        if ((*effect).u.periodic.custom_data == nullptr) {
+            ALOGE("Failed to allocate memory for custom data\n");
+            *status = EX_NULL_POINTER;
+            return false;
+        }
+        memcpy((*effect).u.periodic.custom_data, owtData, numBytes);
+
+        if ((*effect).id != -1) {
+            ALOGE("(*effect).id != -1");
+        }
+
+        /* Create a new OWT waveform to update the PWLE or composite effect. */
+        (*effect).id = -1;
+        if (ioctl(fd, EVIOCSFF, effect) < 0) {
+            ALOGE("Failed to upload effect %d (%d): %s", *outEffectIndex, errno, strerror(errno));
+            delete[] ((*effect).u.periodic.custom_data);
+            *status = EX_ILLEGAL_STATE;
+            return false;
+        }
+
+        if ((*effect).id >= FF_MAX_EFFECTS || (*effect).id < 0) {
+            ALOGE("Invalid waveform index after upload OWT effect: %d", (*effect).id);
+            *status = EX_ILLEGAL_ARGUMENT;
+            return false;
+        }
+        *outEffectIndex = (*effect).id;
+        *status = 0;
+        return true;
+    }
+    bool eraseOwtEffect(int fd, int8_t effectIndex, std::vector<ff_effect> *effect) override {
+        uint32_t effectCountBefore, effectCountAfter, i, successFlush = 0;
+
+        if (effectIndex < WAVEFORM_MAX_PHYSICAL_INDEX) {
+            ALOGE("Invalid waveform index for OWT erase: %d", effectIndex);
+            return false;
+        }
+
+        if (effectIndex < WAVEFORM_MAX_INDEX) {
+            /* Normal situation. Only erase the effect which we just played. */
+            if (ioctl(fd, EVIOCRMFF, effectIndex) < 0) {
+                ALOGE("Failed to erase effect %d (%d): %s", effectIndex, errno, strerror(errno));
+            }
+            for (i = WAVEFORM_MAX_PHYSICAL_INDEX; i < WAVEFORM_MAX_INDEX; i++) {
+                if ((*effect)[i].id == effectIndex) {
+                    (*effect)[i].id = -1;
+                    break;
+                }
+            }
+        } else {
+            /* Flush all non-prestored effects of ff-core and driver. */
+            getEffectCount(&effectCountBefore);
+            for (i = WAVEFORM_MAX_PHYSICAL_INDEX; i < FF_MAX_EFFECTS; i++) {
+                if (ioctl(fd, EVIOCRMFF, i) >= 0) {
+                    successFlush++;
+                }
+            }
+            getEffectCount(&effectCountAfter);
+            ALOGW("Flushed effects: ff: %d; driver: %d -> %d; success: %d", effectIndex,
+                  effectCountBefore, effectCountAfter, successFlush);
+            /* Reset all OWT effect index of HAL. */
+            for (i = WAVEFORM_MAX_PHYSICAL_INDEX; i < WAVEFORM_MAX_INDEX; i++) {
+                (*effect)[i].id = -1;
+            }
+        }
+        return true;
+    }
+
+    void debug(int fd) override { HwApiBase::debug(fd); }
+
+  private:
+    std::ofstream mF0;
+    std::ofstream mF0Offset;
+    std::ofstream mRedc;
+    std::ofstream mQ;
+    std::ifstream mEffectCount;
+    std::ifstream mVibeState;
+    std::ifstream mOwtFreeSpace;
+    std::ofstream mF0CompEnable;
+    std::ofstream mRedcCompEnable;
+    std::ofstream mMinOnOffInterval;
+};
+
+class HwCal : public Vibrator::HwCal, private HwCalBase {
+  private:
+    static constexpr char VERSION[] = "version";
+    static constexpr char F0_CONFIG[] = "f0_measured";
+    static constexpr char REDC_CONFIG[] = "redc_measured";
+    static constexpr char Q_CONFIG[] = "q_measured";
+    static constexpr char TICK_VOLTAGES_CONFIG[] = "v_tick";
+    static constexpr char CLICK_VOLTAGES_CONFIG[] = "v_click";
+    static constexpr char LONG_VOLTAGES_CONFIG[] = "v_long";
+
+    static constexpr uint32_t VERSION_DEFAULT = 2;
+    static constexpr int32_t DEFAULT_FREQUENCY_SHIFT = 0;
+    static constexpr std::array<uint32_t, 2> V_TICK_DEFAULT = {1, 100};
+    static constexpr std::array<uint32_t, 2> V_CLICK_DEFAULT = {1, 100};
+    static constexpr std::array<uint32_t, 2> V_LONG_DEFAULT = {1, 100};
+
+  public:
+    HwCal() {}
+
+    bool getVersion(uint32_t *value) override {
+        if (getPersist(VERSION, value)) {
+            return true;
+        }
+        *value = VERSION_DEFAULT;
+        return true;
+    }
+    bool getLongFrequencyShift(int32_t *value) override {
+        return getProperty("long.frequency.shift", value, DEFAULT_FREQUENCY_SHIFT);
+    }
+    bool getF0(std::string *value) override { return getPersist(F0_CONFIG, value); }
+    bool getRedc(std::string *value) override { return getPersist(REDC_CONFIG, value); }
+    bool getQ(std::string *value) override { return getPersist(Q_CONFIG, value); }
+    bool getTickVolLevels(std::array<uint32_t, 2> *value) override {
+        if (getPersist(TICK_VOLTAGES_CONFIG, value)) {
+            return true;
+        }
+        *value = V_TICK_DEFAULT;
+        return true;
+    }
+    bool getClickVolLevels(std::array<uint32_t, 2> *value) override {
+        if (getPersist(CLICK_VOLTAGES_CONFIG, value)) {
+            return true;
+        }
+        *value = V_CLICK_DEFAULT;
+        return true;
+    }
+    bool getLongVolLevels(std::array<uint32_t, 2> *value) override {
+        if (getPersist(LONG_VOLTAGES_CONFIG, value)) {
+            return true;
+        }
+        *value = V_LONG_DEFAULT;
+        return true;
+    }
+    bool isChirpEnabled() override {
+        bool value;
+        getProperty("chirp.enabled", &value, false);
+        return value;
+    }
+    bool getSupportedPrimitives(uint32_t *value) override {
+        return getProperty("supported_primitives", value, (uint32_t)0);
+    }
+    bool isF0CompEnabled() override {
+        bool value;
+        getProperty("f0.comp.enabled", &value, true);
+        return value;
+    }
+    bool isRedcCompEnabled() override {
+        bool value;
+        getProperty("redc.comp.enabled", &value, true);
+        return value;
+    }
+    void debug(int fd) override { HwCalBase::debug(fd); }
+};
+
+}  // namespace vibrator
+}  // namespace hardware
+}  // namespace android
+}  // namespace aidl

+ 10 - 0
vibrator/cs40l26/TEST_MAPPING

@@ -0,0 +1,10 @@
+{
+  "presubmit": [
+    {
+      "name": "VibratorHalCs40l26TestSuite",
+      "keywords": [
+        "nextgen"
+      ]
+    }
+  ]
+}

+ 1327 - 0
vibrator/cs40l26/Vibrator.cpp

@@ -0,0 +1,1327 @@
+/*
+ * Copyright (C) 2021 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.h"
+
+#include <glob.h>
+#include <hardware/hardware.h>
+#include <hardware/vibrator.h>
+#include <log/log.h>
+#include <stdio.h>
+#include <utils/Trace.h>
+
+#include <cinttypes>
+#include <cmath>
+#include <fstream>
+#include <iostream>
+#include <sstream>
+
+#ifndef ARRAY_SIZE
+#define ARRAY_SIZE(x) (sizeof((x)) / sizeof((x)[0]))
+#endif
+
+namespace aidl {
+namespace android {
+namespace hardware {
+namespace vibrator {
+static constexpr uint8_t FF_CUSTOM_DATA_LEN = 2;
+static constexpr uint16_t FF_CUSTOM_DATA_LEN_MAX_COMP = 2044;  // (COMPOSE_SIZE_MAX + 1) * 8 + 4
+static constexpr uint16_t FF_CUSTOM_DATA_LEN_MAX_PWLE = 2302;
+
+static constexpr uint32_t WAVEFORM_DOUBLE_CLICK_SILENCE_MS = 100;
+
+static constexpr uint32_t WAVEFORM_LONG_VIBRATION_THRESHOLD_MS = 50;
+
+static constexpr uint8_t VOLTAGE_SCALE_MAX = 100;
+
+static constexpr int8_t MAX_COLD_START_LATENCY_MS = 6;  // I2C Transaction + DSP Return-From-Standby
+static constexpr uint32_t MIN_ON_OFF_INTERVAL_US = 8500;  // SVC initialization time
+static constexpr int8_t MAX_PAUSE_TIMING_ERROR_MS = 1;  // ALERT Irq Handling
+static constexpr uint32_t MAX_TIME_MS = UINT16_MAX;
+
+static constexpr auto ASYNC_COMPLETION_TIMEOUT = std::chrono::milliseconds(100);
+static constexpr auto POLLING_TIMEOUT = 20;
+static constexpr int32_t COMPOSE_DELAY_MAX_MS = 10000;
+
+/* nsections is 8 bits. Need to preserve 1 section for the first delay before the first effect. */
+static constexpr int32_t COMPOSE_SIZE_MAX = 254;
+static constexpr int32_t COMPOSE_PWLE_SIZE_MAX_DEFAULT = 127;
+
+// Measured resonant frequency, f0_measured, is represented by Q10.14 fixed
+// point format on cs40l26 devices. The expression to calculate f0 is:
+//   f0 = f0_measured / 2^Q14_BIT_SHIFT
+// See the LRA Calibration Support documentation for more details.
+static constexpr int32_t Q14_BIT_SHIFT = 14;
+
+// Measured Q factor, q_measured, is represented by Q8.16 fixed
+// point format on cs40l26 devices. The expression to calculate q is:
+//   q = q_measured / 2^Q16_BIT_SHIFT
+// See the LRA Calibration Support documentation for more details.
+static constexpr int32_t Q16_BIT_SHIFT = 16;
+
+static constexpr int32_t COMPOSE_PWLE_PRIMITIVE_DURATION_MAX_MS = 16383;
+
+static constexpr uint32_t WT_LEN_CALCD = 0x00800000;
+static constexpr uint8_t PWLE_CHIRP_BIT = 0x8;  // Dynamic/static frequency and voltage
+static constexpr uint8_t PWLE_BRAKE_BIT = 0x4;
+static constexpr uint8_t PWLE_AMP_REG_BIT = 0x2;
+
+static constexpr float PWLE_LEVEL_MIN = 0.0;
+static constexpr float PWLE_LEVEL_MAX = 1.0;
+static constexpr float CS40L26_PWLE_LEVEL_MIX = -1.0;
+static constexpr float CS40L26_PWLE_LEVEL_MAX = 0.9995118;
+static constexpr float PWLE_FREQUENCY_RESOLUTION_HZ = 1.00;
+static constexpr float PWLE_FREQUENCY_MIN_HZ = 1.00;
+static constexpr float PWLE_FREQUENCY_MAX_HZ = 1000.00;
+static constexpr float PWLE_BW_MAP_SIZE =
+        1 + ((PWLE_FREQUENCY_MAX_HZ - PWLE_FREQUENCY_MIN_HZ) / PWLE_FREQUENCY_RESOLUTION_HZ);
+
+static uint16_t amplitudeToScale(float amplitude, float maximum) {
+    float ratio = 100; /* Unit: % */
+    if (maximum != 0)
+        ratio = amplitude / maximum * 100;
+
+    if (maximum == 0 || ratio > 100)
+        ratio = 100;
+
+    return std::round(ratio);
+}
+
+enum WaveformBankID : uint8_t {
+    RAM_WVFRM_BANK,
+    ROM_WVFRM_BANK,
+    OWT_WVFRM_BANK,
+};
+
+enum WaveformIndex : uint16_t {
+    /* Physical waveform */
+    WAVEFORM_LONG_VIBRATION_EFFECT_INDEX = 0,
+    WAVEFORM_RESERVED_INDEX_1 = 1,
+    WAVEFORM_CLICK_INDEX = 2,
+    WAVEFORM_SHORT_VIBRATION_EFFECT_INDEX = 3,
+    WAVEFORM_THUD_INDEX = 4,
+    WAVEFORM_SPIN_INDEX = 5,
+    WAVEFORM_QUICK_RISE_INDEX = 6,
+    WAVEFORM_SLOW_RISE_INDEX = 7,
+    WAVEFORM_QUICK_FALL_INDEX = 8,
+    WAVEFORM_LIGHT_TICK_INDEX = 9,
+    WAVEFORM_LOW_TICK_INDEX = 10,
+    WAVEFORM_RESERVED_MFG_1,
+    WAVEFORM_RESERVED_MFG_2,
+    WAVEFORM_RESERVED_MFG_3,
+    WAVEFORM_MAX_PHYSICAL_INDEX,
+    /* OWT waveform */
+    WAVEFORM_COMPOSE = WAVEFORM_MAX_PHYSICAL_INDEX,
+    WAVEFORM_PWLE,
+    /*
+     * Refer to <linux/input.h>, the WAVEFORM_MAX_INDEX must not exceed 96.
+     * #define FF_GAIN		0x60  // 96 in decimal
+     * #define FF_MAX_EFFECTS	FF_GAIN
+     */
+    WAVEFORM_MAX_INDEX,
+};
+
+std::vector<CompositePrimitive> defaultSupportedPrimitives = {
+        ndk::enum_range<CompositePrimitive>().begin(), ndk::enum_range<CompositePrimitive>().end()};
+
+enum vibe_state {
+    VIBE_STATE_STOPPED = 0,
+    VIBE_STATE_HAPTIC,
+    VIBE_STATE_ASP,
+};
+
+std::mutex mActiveId_mutex;  // protects mActiveId
+
+static int min(int x, int y) {
+    return x < y ? x : y;
+}
+
+static int floatToUint16(float input, uint16_t *output, float scale, float min, float max) {
+    if (input < min || input > max)
+        return -ERANGE;
+
+    *output = roundf(input * scale);
+    return 0;
+}
+
+struct dspmem_chunk {
+    uint8_t *head;
+    uint8_t *current;
+    uint8_t *max;
+    int bytes;
+
+    uint32_t cache;
+    int cachebits;
+};
+
+static dspmem_chunk *dspmem_chunk_create(void *data, int size) {
+    auto ch = new dspmem_chunk{
+            .head = reinterpret_cast<uint8_t *>(data),
+            .current = reinterpret_cast<uint8_t *>(data),
+            .max = reinterpret_cast<uint8_t *>(data) + size,
+    };
+
+    return ch;
+}
+
+static bool dspmem_chunk_end(struct dspmem_chunk *ch) {
+    return ch->current == ch->max;
+}
+
+static int dspmem_chunk_bytes(struct dspmem_chunk *ch) {
+    return ch->bytes;
+}
+
+static int dspmem_chunk_write(struct dspmem_chunk *ch, int nbits, uint32_t val) {
+    int nwrite, i;
+
+    nwrite = min(24 - ch->cachebits, nbits);
+    ch->cache <<= nwrite;
+    ch->cache |= val >> (nbits - nwrite);
+    ch->cachebits += nwrite;
+    nbits -= nwrite;
+
+    if (ch->cachebits == 24) {
+        if (dspmem_chunk_end(ch))
+            return -ENOSPC;
+
+        ch->cache &= 0xFFFFFF;
+        for (i = 0; i < sizeof(ch->cache); i++, ch->cache <<= 8)
+            *ch->current++ = (ch->cache & 0xFF000000) >> 24;
+
+        ch->bytes += sizeof(ch->cache);
+        ch->cachebits = 0;
+    }
+
+    if (nbits)
+        return dspmem_chunk_write(ch, nbits, val);
+
+    return 0;
+}
+
+static int dspmem_chunk_flush(struct dspmem_chunk *ch) {
+    if (!ch->cachebits)
+        return 0;
+
+    return dspmem_chunk_write(ch, 24 - ch->cachebits, 0);
+}
+
+Vibrator::Vibrator(std::unique_ptr<HwApi> hwapi, std::unique_ptr<HwCal> hwcal)
+    : mHwApi(std::move(hwapi)), mHwCal(std::move(hwcal)), mAsyncHandle(std::async([] {})) {
+    int32_t longFrequencyShift;
+    std::string caldata{8, '0'};
+    uint32_t calVer;
+
+    const char *inputEventName = std::getenv("INPUT_EVENT_NAME");
+    const char *inputEventPathName = std::getenv("INPUT_EVENT_PATH");
+    if ((strstr(inputEventName, "cs40l26") != nullptr) ||
+        (strstr(inputEventName, "cs40l26_dual_input") != nullptr)) {
+        glob_t inputEventPaths;
+        int fd = -1;
+        int ret;
+        uint32_t val = 0;
+        char str[20] = {0x00};
+        for (uint8_t retry = 0; retry < 10; retry++) {
+            ret = glob(inputEventPathName, 0, nullptr, &inputEventPaths);
+            if (ret) {
+                ALOGE("Fail to get input event paths (%d): %s", errno, strerror(errno));
+            } else {
+                for (int i = 0; i < inputEventPaths.gl_pathc; i++) {
+                    fd = TEMP_FAILURE_RETRY(open(inputEventPaths.gl_pathv[i], O_RDWR));
+                    if (fd > 0) {
+                        if (ioctl(fd, EVIOCGBIT(0, sizeof(val)), &val) > 0 &&
+                            (val & (1 << EV_FF)) && ioctl(fd, EVIOCGNAME(sizeof(str)), &str) > 0 &&
+                            strstr(str, inputEventName) != nullptr) {
+                            mInputFd.reset(fd);
+                            ALOGI("Control %s through %s", inputEventName,
+                                  inputEventPaths.gl_pathv[i]);
+                            break;
+                        }
+                        close(fd);
+                    }
+                }
+            }
+
+            if (ret == 0) {
+                globfree(&inputEventPaths);
+            }
+            if (mInputFd.ok()) {
+                break;
+            }
+
+            sleep(1);
+            ALOGW("Retry #%d to search in %zu input devices.", retry, inputEventPaths.gl_pathc);
+        }
+
+        if (!mInputFd.ok()) {
+            ALOGE("Fail to get an input event with name %s", inputEventName);
+        }
+    } else {
+        ALOGE("The input name %s is not cs40l26_input or cs40l26_dual_input", inputEventName);
+    }
+
+    mFfEffects.resize(WAVEFORM_MAX_INDEX);
+    mEffectDurations.resize(WAVEFORM_MAX_INDEX);
+    mEffectDurations = {
+            1000, 100, 30, 1000, 300, 130, 150, 500, 100, 15, 20, 1000, 1000, 1000,
+    }; /* 11+3 waveforms. The duration must < UINT16_MAX */
+
+    uint8_t effectIndex;
+    for (effectIndex = 0; effectIndex < WAVEFORM_MAX_INDEX; effectIndex++) {
+        if (effectIndex < WAVEFORM_MAX_PHYSICAL_INDEX) {
+            /* Initialize physical waveforms. */
+            mFfEffects[effectIndex] = {
+                    .type = FF_PERIODIC,
+                    .id = -1,
+                    .replay.length = static_cast<uint16_t>(mEffectDurations[effectIndex]),
+                    .u.periodic.waveform = FF_CUSTOM,
+                    .u.periodic.custom_data = new int16_t[2]{RAM_WVFRM_BANK, effectIndex},
+                    .u.periodic.custom_len = FF_CUSTOM_DATA_LEN,
+            };
+            // Bypass the waveform update due to different input name
+            if ((strstr(inputEventName, "cs40l26") != nullptr) ||
+                (strstr(inputEventName, "cs40l26_dual_input") != nullptr)) {
+                if (!mHwApi->setFFEffect(
+                            mInputFd, &mFfEffects[effectIndex],
+                            static_cast<uint16_t>(mFfEffects[effectIndex].replay.length))) {
+                    ALOGE("Failed upload effect %d (%d): %s", effectIndex, errno, strerror(errno));
+                }
+            }
+            if (mFfEffects[effectIndex].id != effectIndex) {
+                ALOGW("Unexpected effect index: %d -> %d", effectIndex, mFfEffects[effectIndex].id);
+            }
+        } else {
+            /* Initiate placeholders for OWT effects. */
+            mFfEffects[effectIndex] = {
+                    .type = FF_PERIODIC,
+                    .id = -1,
+                    .replay.length = 0,
+                    .u.periodic.waveform = FF_CUSTOM,
+                    .u.periodic.custom_data = nullptr,
+                    .u.periodic.custom_len = 0,
+            };
+        }
+    }
+
+    if (mHwCal->getF0(&caldata)) {
+        mHwApi->setF0(caldata);
+    }
+    if (mHwCal->getRedc(&caldata)) {
+        mHwApi->setRedc(caldata);
+    }
+    if (mHwCal->getQ(&caldata)) {
+        mHwApi->setQ(caldata);
+    }
+
+    mHwCal->getLongFrequencyShift(&longFrequencyShift);
+    if (longFrequencyShift > 0) {
+        mF0Offset = longFrequencyShift * std::pow(2, 14);
+    } else if (longFrequencyShift < 0) {
+        mF0Offset = std::pow(2, 24) - std::abs(longFrequencyShift) * std::pow(2, 14);
+    } else {
+        mF0Offset = 0;
+    }
+
+    mHwCal->getVersion(&calVer);
+    if (calVer == 2) {
+        mHwCal->getTickVolLevels(&mTickEffectVol);
+        mHwCal->getClickVolLevels(&mClickEffectVol);
+        mHwCal->getLongVolLevels(&mLongEffectVol);
+    } else {
+        ALOGD("Unsupported calibration version: %u!", calVer);
+    }
+
+    mHwApi->setF0CompEnable(mHwCal->isF0CompEnabled());
+    mHwApi->setRedcCompEnable(mHwCal->isRedcCompEnabled());
+
+    mIsUnderExternalControl = false;
+
+    mIsChirpEnabled = mHwCal->isChirpEnabled();
+
+    mHwCal->getSupportedPrimitives(&mSupportedPrimitivesBits);
+    if (mSupportedPrimitivesBits > 0) {
+        for (auto e : defaultSupportedPrimitives) {
+            if (mSupportedPrimitivesBits & (1 << uint32_t(e))) {
+                mSupportedPrimitives.emplace_back(e);
+            }
+        }
+    } else {
+        for (auto e : defaultSupportedPrimitives) {
+            mSupportedPrimitivesBits |= (1 << uint32_t(e));
+        }
+        mSupportedPrimitives = defaultSupportedPrimitives;
+    }
+
+    mHwApi->setMinOnOffInterval(MIN_ON_OFF_INTERVAL_US);
+}
+
+ndk::ScopedAStatus Vibrator::getCapabilities(int32_t *_aidl_return) {
+    ATRACE_NAME("Vibrator::getCapabilities");
+
+    int32_t ret = IVibrator::CAP_ON_CALLBACK | IVibrator::CAP_PERFORM_CALLBACK |
+                  IVibrator::CAP_AMPLITUDE_CONTROL | IVibrator::CAP_GET_RESONANT_FREQUENCY |
+                  IVibrator::CAP_GET_Q_FACTOR;
+    if (hasHapticAlsaDevice()) {
+        ret |= IVibrator::CAP_EXTERNAL_CONTROL;
+    } else {
+        ALOGE("No haptics ALSA device");
+    }
+    if (mHwApi->hasOwtFreeSpace()) {
+        ret |= IVibrator::CAP_COMPOSE_EFFECTS;
+        if (mIsChirpEnabled) {
+            ret |= IVibrator::CAP_FREQUENCY_CONTROL | IVibrator::CAP_COMPOSE_PWLE_EFFECTS;
+        }
+    }
+    *_aidl_return = ret;
+    return ndk::ScopedAStatus::ok();
+}
+
+ndk::ScopedAStatus Vibrator::off() {
+    ATRACE_NAME("Vibrator::off");
+    bool ret{true};
+    const std::scoped_lock<std::mutex> lock(mActiveId_mutex);
+
+    if (mActiveId >= 0) {
+        /* Stop the active effect. */
+        if (!mHwApi->setFFPlay(mInputFd, mActiveId, false)) {
+            ALOGE("Failed to stop effect %d (%d): %s", mActiveId, errno, strerror(errno));
+            ret = false;
+        }
+
+        if ((mActiveId >= WAVEFORM_MAX_PHYSICAL_INDEX) &&
+            (!mHwApi->eraseOwtEffect(mInputFd, mActiveId, &mFfEffects))) {
+            ALOGE("Failed to clean up the composed effect %d", mActiveId);
+            ret = false;
+        }
+    } else {
+        ALOGV("Vibrator is already off");
+    }
+
+    mActiveId = -1;
+    setGlobalAmplitude(false);
+    if (mF0Offset) {
+        mHwApi->setF0Offset(0);
+    }
+
+    if (ret) {
+        return ndk::ScopedAStatus::ok();
+    } else {
+        return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE);
+    }
+}
+
+ndk::ScopedAStatus Vibrator::on(int32_t timeoutMs,
+                                const std::shared_ptr<IVibratorCallback> &callback) {
+    ATRACE_NAME("Vibrator::on");
+    if (timeoutMs > MAX_TIME_MS) {
+        return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
+    }
+    const uint16_t index = (timeoutMs < WAVEFORM_LONG_VIBRATION_THRESHOLD_MS)
+                                   ? WAVEFORM_SHORT_VIBRATION_EFFECT_INDEX
+                                   : WAVEFORM_LONG_VIBRATION_EFFECT_INDEX;
+    if (MAX_COLD_START_LATENCY_MS <= MAX_TIME_MS - timeoutMs) {
+        timeoutMs += MAX_COLD_START_LATENCY_MS;
+    }
+    setGlobalAmplitude(true);
+    if (mF0Offset) {
+        mHwApi->setF0Offset(mF0Offset);
+    }
+    return on(timeoutMs, index, nullptr /*ignored*/, callback);
+}
+
+ndk::ScopedAStatus Vibrator::perform(Effect effect, EffectStrength strength,
+                                     const std::shared_ptr<IVibratorCallback> &callback,
+                                     int32_t *_aidl_return) {
+    ATRACE_NAME("Vibrator::perform");
+    return performEffect(effect, strength, callback, _aidl_return);
+}
+
+ndk::ScopedAStatus Vibrator::getSupportedEffects(std::vector<Effect> *_aidl_return) {
+    *_aidl_return = {Effect::TEXTURE_TICK, Effect::TICK, Effect::CLICK, Effect::HEAVY_CLICK,
+                     Effect::DOUBLE_CLICK};
+    return ndk::ScopedAStatus::ok();
+}
+
+ndk::ScopedAStatus Vibrator::setAmplitude(float amplitude) {
+    ATRACE_NAME("Vibrator::setAmplitude");
+    if (amplitude <= 0.0f || amplitude > 1.0f) {
+        return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
+    }
+
+    mLongEffectScale = amplitude;
+    if (!isUnderExternalControl()) {
+        return setGlobalAmplitude(true);
+    } else {
+        return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION);
+    }
+}
+
+ndk::ScopedAStatus Vibrator::setExternalControl(bool enabled) {
+    ATRACE_NAME("Vibrator::setExternalControl");
+    setGlobalAmplitude(enabled);
+
+    if (mHasHapticAlsaDevice || mConfigHapticAlsaDeviceDone || hasHapticAlsaDevice()) {
+        if (!mHwApi->setHapticPcmAmp(&mHapticPcm, enabled, mCard, mDevice)) {
+            ALOGE("Failed to %s haptic pcm device: %d", (enabled ? "enable" : "disable"), mDevice);
+            return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE);
+        }
+    } else {
+        ALOGE("No haptics ALSA device");
+        return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE);
+    }
+
+    mIsUnderExternalControl = enabled;
+    return ndk::ScopedAStatus::ok();
+}
+
+ndk::ScopedAStatus Vibrator::getCompositionDelayMax(int32_t *maxDelayMs) {
+    ATRACE_NAME("Vibrator::getCompositionDelayMax");
+    *maxDelayMs = COMPOSE_DELAY_MAX_MS;
+    return ndk::ScopedAStatus::ok();
+}
+
+ndk::ScopedAStatus Vibrator::getCompositionSizeMax(int32_t *maxSize) {
+    ATRACE_NAME("Vibrator::getCompositionSizeMax");
+    *maxSize = COMPOSE_SIZE_MAX;
+    return ndk::ScopedAStatus::ok();
+}
+
+ndk::ScopedAStatus Vibrator::getSupportedPrimitives(std::vector<CompositePrimitive> *supported) {
+    *supported = mSupportedPrimitives;
+    return ndk::ScopedAStatus::ok();
+}
+
+ndk::ScopedAStatus Vibrator::getPrimitiveDuration(CompositePrimitive primitive,
+                                                  int32_t *durationMs) {
+    ndk::ScopedAStatus status;
+    uint32_t effectIndex;
+    if (primitive != CompositePrimitive::NOOP) {
+        status = getPrimitiveDetails(primitive, &effectIndex);
+        if (!status.isOk()) {
+            return status;
+        }
+
+        *durationMs = mEffectDurations[effectIndex];
+    } else {
+        *durationMs = 0;
+    }
+    return ndk::ScopedAStatus::ok();
+}
+
+ndk::ScopedAStatus Vibrator::compose(const std::vector<CompositeEffect> &composite,
+                                     const std::shared_ptr<IVibratorCallback> &callback) {
+    ATRACE_NAME("Vibrator::compose");
+    uint16_t size;
+    uint16_t nextEffectDelay;
+
+    auto ch = dspmem_chunk_create(new uint8_t[FF_CUSTOM_DATA_LEN_MAX_COMP]{0x00},
+                                  FF_CUSTOM_DATA_LEN_MAX_COMP);
+
+    if (composite.size() > COMPOSE_SIZE_MAX || composite.empty()) {
+        return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
+    }
+
+    /* Check if there is a wait before the first effect. */
+    nextEffectDelay = composite.front().delayMs;
+    if (nextEffectDelay > COMPOSE_DELAY_MAX_MS || nextEffectDelay < 0) {
+        return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
+    } else if (nextEffectDelay > 0) {
+        size = composite.size() + 1;
+    } else {
+        size = composite.size();
+    }
+
+    dspmem_chunk_write(ch, 8, 0);                      /* Padding */
+    dspmem_chunk_write(ch, 8, (uint8_t)(0xFF & size)); /* nsections */
+    dspmem_chunk_write(ch, 8, 0);                      /* repeat */
+    uint8_t header_count = dspmem_chunk_bytes(ch);
+
+    /* Insert 1 section for a wait before the first effect. */
+    if (nextEffectDelay) {
+        dspmem_chunk_write(ch, 32, 0); /* amplitude, index, repeat & flags */
+        dspmem_chunk_write(ch, 16, (uint16_t)(0xFFFF & nextEffectDelay)); /* delay */
+    }
+
+    for (uint32_t i_curr = 0, i_next = 1; i_curr < composite.size(); i_curr++, i_next++) {
+        auto &e_curr = composite[i_curr];
+        uint32_t effectIndex = 0;
+        uint32_t effectVolLevel = 0;
+        if (e_curr.scale < 0.0f || e_curr.scale > 1.0f) {
+            return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
+        }
+
+        if (e_curr.primitive != CompositePrimitive::NOOP) {
+            ndk::ScopedAStatus status;
+            status = getPrimitiveDetails(e_curr.primitive, &effectIndex);
+            if (!status.isOk()) {
+                return status;
+            }
+            effectVolLevel = intensityToVolLevel(e_curr.scale, effectIndex);
+        }
+
+        /* Fetch the next composite effect delay and fill into the current section */
+        nextEffectDelay = 0;
+        if (i_next < composite.size()) {
+            auto &e_next = composite[i_next];
+            int32_t delay = e_next.delayMs;
+
+            if (delay > COMPOSE_DELAY_MAX_MS || delay < 0) {
+                return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
+            }
+            nextEffectDelay = delay;
+        }
+
+        if (effectIndex == 0 && nextEffectDelay == 0) {
+            return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
+        }
+
+        dspmem_chunk_write(ch, 8, (uint8_t)(0xFF & effectVolLevel));      /* amplitude */
+        dspmem_chunk_write(ch, 8, (uint8_t)(0xFF & effectIndex));         /* index */
+        dspmem_chunk_write(ch, 8, 0);                                     /* repeat */
+        dspmem_chunk_write(ch, 8, 0);                                     /* flags */
+        dspmem_chunk_write(ch, 16, (uint16_t)(0xFFFF & nextEffectDelay)); /* delay */
+    }
+    dspmem_chunk_flush(ch);
+    if (header_count == dspmem_chunk_bytes(ch)) {
+        return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
+    } else {
+        return performEffect(WAVEFORM_MAX_INDEX /*ignored*/, VOLTAGE_SCALE_MAX /*ignored*/, ch,
+                             callback);
+    }
+}
+
+ndk::ScopedAStatus Vibrator::on(uint32_t timeoutMs, uint32_t effectIndex, dspmem_chunk *ch,
+                                const std::shared_ptr<IVibratorCallback> &callback) {
+    ndk::ScopedAStatus status = ndk::ScopedAStatus::ok();
+
+    if (effectIndex >= FF_MAX_EFFECTS) {
+        ALOGE("Invalid waveform index %d", effectIndex);
+        return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
+    }
+    if (mAsyncHandle.wait_for(ASYNC_COMPLETION_TIMEOUT) != std::future_status::ready) {
+        ALOGE("Previous vibration pending: prev: %d, curr: %d", mActiveId, effectIndex);
+        return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE);
+    }
+
+    if (ch) {
+        /* Upload OWT effect. */
+        if (ch->head == nullptr) {
+            ALOGE("Invalid OWT bank");
+            delete ch;
+            return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
+        }
+        bool isPwle = (*reinterpret_cast<uint16_t *>(ch->head) != 0x0000);
+        effectIndex = isPwle ? WAVEFORM_PWLE : WAVEFORM_COMPOSE;
+
+        uint32_t freeBytes;
+        mHwApi->getOwtFreeSpace(&freeBytes);
+        if (dspmem_chunk_bytes(ch) > freeBytes) {
+            ALOGE("Invalid OWT length: Effect %d: %d > %d!", effectIndex, dspmem_chunk_bytes(ch),
+                  freeBytes);
+            delete ch;
+            return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
+        }
+        int errorStatus;
+        if (!mHwApi->uploadOwtEffect(mInputFd, ch->head, dspmem_chunk_bytes(ch),
+                                     &mFfEffects[effectIndex], &effectIndex, &errorStatus)) {
+            delete ch;
+            ALOGE("Invalid uploadOwtEffect");
+            return ndk::ScopedAStatus::fromExceptionCode(errorStatus);
+        }
+        delete ch;
+
+    } else if (effectIndex == WAVEFORM_SHORT_VIBRATION_EFFECT_INDEX ||
+               effectIndex == WAVEFORM_LONG_VIBRATION_EFFECT_INDEX) {
+        /* Update duration for long/short vibration. */
+        mFfEffects[effectIndex].replay.length = static_cast<uint16_t>(timeoutMs);
+        if (!mHwApi->setFFEffect(mInputFd, &mFfEffects[effectIndex],
+                                 static_cast<uint16_t>(timeoutMs))) {
+            ALOGE("Failed to edit effect %d (%d): %s", effectIndex, errno, strerror(errno));
+            return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE);
+        }
+    }
+
+    const std::scoped_lock<std::mutex> lock(mActiveId_mutex);
+    mActiveId = effectIndex;
+    /* Play the event now. */
+    if (!mHwApi->setFFPlay(mInputFd, effectIndex, true)) {
+        ALOGE("Failed to play effect %d (%d): %s", effectIndex, errno, strerror(errno));
+        return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE);
+    }
+
+    mAsyncHandle = std::async(&Vibrator::waitForComplete, this, callback);
+    return ndk::ScopedAStatus::ok();
+}
+
+ndk::ScopedAStatus Vibrator::setEffectAmplitude(float amplitude, float maximum) {
+    uint16_t scale = amplitudeToScale(amplitude, maximum);
+    if (!mHwApi->setFFGain(mInputFd, scale)) {
+        ALOGE("Failed to set the gain to %u (%d): %s", scale, errno, strerror(errno));
+        return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE);
+    }
+    return ndk::ScopedAStatus::ok();
+}
+
+ndk::ScopedAStatus Vibrator::setGlobalAmplitude(bool set) {
+    uint8_t amplitude = set ? roundf(mLongEffectScale * mLongEffectVol[1]) : VOLTAGE_SCALE_MAX;
+    if (!set) {
+        mLongEffectScale = 1.0;  // Reset the scale for the later new effect.
+    }
+    return setEffectAmplitude(amplitude, VOLTAGE_SCALE_MAX);
+}
+
+ndk::ScopedAStatus Vibrator::getSupportedAlwaysOnEffects(std::vector<Effect> * /*_aidl_return*/) {
+    return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION);
+}
+
+ndk::ScopedAStatus Vibrator::alwaysOnEnable(int32_t /*id*/, Effect /*effect*/,
+                                            EffectStrength /*strength*/) {
+    return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION);
+}
+ndk::ScopedAStatus Vibrator::alwaysOnDisable(int32_t /*id*/) {
+    return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION);
+}
+
+ndk::ScopedAStatus Vibrator::getResonantFrequency(float *resonantFreqHz) {
+    std::string caldata{8, '0'};
+    if (!mHwCal->getF0(&caldata)) {
+        ALOGE("Failed to get resonant frequency (%d): %s", errno, strerror(errno));
+        return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE);
+    }
+    *resonantFreqHz = static_cast<float>(std::stoul(caldata, nullptr, 16)) / (1 << Q14_BIT_SHIFT);
+
+    return ndk::ScopedAStatus::ok();
+}
+
+ndk::ScopedAStatus Vibrator::getQFactor(float *qFactor) {
+    std::string caldata{8, '0'};
+    if (!mHwCal->getQ(&caldata)) {
+        ALOGE("Failed to get q factor (%d): %s", errno, strerror(errno));
+        return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE);
+    }
+    *qFactor = static_cast<float>(std::stoul(caldata, nullptr, 16)) / (1 << Q16_BIT_SHIFT);
+
+    return ndk::ScopedAStatus::ok();
+}
+
+ndk::ScopedAStatus Vibrator::getFrequencyResolution(float *freqResolutionHz) {
+    int32_t capabilities;
+    Vibrator::getCapabilities(&capabilities);
+    if (capabilities & IVibrator::CAP_FREQUENCY_CONTROL) {
+        *freqResolutionHz = PWLE_FREQUENCY_RESOLUTION_HZ;
+        return ndk::ScopedAStatus::ok();
+    } else {
+        return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION);
+    }
+}
+
+ndk::ScopedAStatus Vibrator::getFrequencyMinimum(float *freqMinimumHz) {
+    int32_t capabilities;
+    Vibrator::getCapabilities(&capabilities);
+    if (capabilities & IVibrator::CAP_FREQUENCY_CONTROL) {
+        *freqMinimumHz = PWLE_FREQUENCY_MIN_HZ;
+        return ndk::ScopedAStatus::ok();
+    } else {
+        return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION);
+    }
+}
+
+ndk::ScopedAStatus Vibrator::getBandwidthAmplitudeMap(std::vector<float> *_aidl_return) {
+    // TODO(b/170919640): complete implementation
+    int32_t capabilities;
+    Vibrator::getCapabilities(&capabilities);
+    if (capabilities & IVibrator::CAP_FREQUENCY_CONTROL) {
+        std::vector<float> bandwidthAmplitudeMap(PWLE_BW_MAP_SIZE, 1.0);
+        *_aidl_return = bandwidthAmplitudeMap;
+        return ndk::ScopedAStatus::ok();
+    } else {
+        return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION);
+    }
+}
+
+ndk::ScopedAStatus Vibrator::getPwlePrimitiveDurationMax(int32_t *durationMs) {
+    int32_t capabilities;
+    Vibrator::getCapabilities(&capabilities);
+    if (capabilities & IVibrator::CAP_COMPOSE_PWLE_EFFECTS) {
+        *durationMs = COMPOSE_PWLE_PRIMITIVE_DURATION_MAX_MS;
+        return ndk::ScopedAStatus::ok();
+    } else {
+        return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION);
+    }
+}
+
+ndk::ScopedAStatus Vibrator::getPwleCompositionSizeMax(int32_t *maxSize) {
+    int32_t capabilities;
+    Vibrator::getCapabilities(&capabilities);
+    if (capabilities & IVibrator::CAP_COMPOSE_PWLE_EFFECTS) {
+        *maxSize = COMPOSE_PWLE_SIZE_MAX_DEFAULT;
+        return ndk::ScopedAStatus::ok();
+    } else {
+        return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION);
+    }
+}
+
+ndk::ScopedAStatus Vibrator::getSupportedBraking(std::vector<Braking> *supported) {
+    int32_t capabilities;
+    Vibrator::getCapabilities(&capabilities);
+    if (capabilities & IVibrator::CAP_COMPOSE_PWLE_EFFECTS) {
+        *supported = {
+                Braking::NONE,
+        };
+        return ndk::ScopedAStatus::ok();
+    } else {
+        return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION);
+    }
+}
+
+static void resetPreviousEndAmplitudeEndFrequency(float *prevEndAmplitude,
+                                                  float *prevEndFrequency) {
+    const float reset = -1.0;
+    *prevEndAmplitude = reset;
+    *prevEndFrequency = reset;
+}
+
+static void incrementIndex(int *index) {
+    *index += 1;
+}
+
+static void constructPwleSegment(dspmem_chunk *ch, uint16_t delay, uint16_t amplitude,
+                                 uint16_t frequency, uint8_t flags, uint32_t vbemfTarget = 0) {
+    dspmem_chunk_write(ch, 16, delay);
+    dspmem_chunk_write(ch, 12, amplitude);
+    dspmem_chunk_write(ch, 12, frequency);
+    /* feature flags to control the chirp, CLAB braking, back EMF amplitude regulation */
+    dspmem_chunk_write(ch, 8, (flags | 1) << 4);
+    if (flags & PWLE_AMP_REG_BIT) {
+        dspmem_chunk_write(ch, 24, vbemfTarget); /* target back EMF voltage */
+    }
+}
+
+static int constructActiveSegment(dspmem_chunk *ch, int duration, float amplitude, float frequency,
+                                  bool chirp) {
+    uint16_t delay = 0;
+    uint16_t amp = 0;
+    uint16_t freq = 0;
+    uint8_t flags = 0x0;
+    if ((floatToUint16(duration, &delay, 4, 0.0f, COMPOSE_PWLE_PRIMITIVE_DURATION_MAX_MS) < 0) ||
+        (floatToUint16(amplitude, &amp, 2048, CS40L26_PWLE_LEVEL_MIX, CS40L26_PWLE_LEVEL_MAX) <
+         0) ||
+        (floatToUint16(frequency, &freq, 4, PWLE_FREQUENCY_MIN_HZ, PWLE_FREQUENCY_MAX_HZ) < 0)) {
+        ALOGE("Invalid argument: %d, %f, %f", duration, amplitude, frequency);
+        return -ERANGE;
+    }
+    if (chirp) {
+        flags |= PWLE_CHIRP_BIT;
+    }
+    constructPwleSegment(ch, delay, amp, freq, flags, 0 /*ignored*/);
+    return 0;
+}
+
+static int constructBrakingSegment(dspmem_chunk *ch, int duration, Braking brakingType) {
+    uint16_t delay = 0;
+    uint16_t freq = 0;
+    uint8_t flags = 0x00;
+    if (floatToUint16(duration, &delay, 4, 0.0f, COMPOSE_PWLE_PRIMITIVE_DURATION_MAX_MS) < 0) {
+        ALOGE("Invalid argument: %d", duration);
+        return -ERANGE;
+    }
+    floatToUint16(PWLE_FREQUENCY_MIN_HZ, &freq, 4, PWLE_FREQUENCY_MIN_HZ, PWLE_FREQUENCY_MAX_HZ);
+    if (static_cast<std::underlying_type<Braking>::type>(brakingType)) {
+        flags |= PWLE_BRAKE_BIT;
+    }
+
+    constructPwleSegment(ch, delay, 0 /*ignored*/, freq, flags, 0 /*ignored*/);
+    return 0;
+}
+
+static void updateWLength(dspmem_chunk *ch, uint32_t totalDuration) {
+    totalDuration *= 8;            /* Unit: 0.125 ms (since wlength played @ 8kHz). */
+    totalDuration |= WT_LEN_CALCD; /* Bit 23 is for WT_LEN_CALCD; Bit 22 is for WT_INDEFINITE. */
+    *(ch->head + 0) = (totalDuration >> 24) & 0xFF;
+    *(ch->head + 1) = (totalDuration >> 16) & 0xFF;
+    *(ch->head + 2) = (totalDuration >> 8) & 0xFF;
+    *(ch->head + 3) = totalDuration & 0xFF;
+}
+
+static void updateNSection(dspmem_chunk *ch, int segmentIdx) {
+    *(ch->head + 7) |= (0xF0 & segmentIdx) >> 4; /* Bit 4 to 7 */
+    *(ch->head + 9) |= (0x0F & segmentIdx) << 4; /* Bit 3 to 0 */
+}
+
+ndk::ScopedAStatus Vibrator::composePwle(const std::vector<PrimitivePwle> &composite,
+                                         const std::shared_ptr<IVibratorCallback> &callback) {
+    ATRACE_NAME("Vibrator::composePwle");
+    int32_t capabilities;
+
+    Vibrator::getCapabilities(&capabilities);
+    if ((capabilities & IVibrator::CAP_COMPOSE_PWLE_EFFECTS) == 0) {
+        return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION);
+    }
+
+    if (composite.empty() || composite.size() > COMPOSE_PWLE_SIZE_MAX_DEFAULT) {
+        return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
+    }
+
+    std::vector<Braking> supported;
+    Vibrator::getSupportedBraking(&supported);
+    bool isClabSupported =
+            std::find(supported.begin(), supported.end(), Braking::CLAB) != supported.end();
+
+    int segmentIdx = 0;
+    uint32_t totalDuration = 0;
+    float prevEndAmplitude;
+    float prevEndFrequency;
+    resetPreviousEndAmplitudeEndFrequency(&prevEndAmplitude, &prevEndFrequency);
+    auto ch = dspmem_chunk_create(new uint8_t[FF_CUSTOM_DATA_LEN_MAX_PWLE]{0x00},
+                                  FF_CUSTOM_DATA_LEN_MAX_PWLE);
+    bool chirp = false;
+
+    dspmem_chunk_write(ch, 24, 0x000000); /* Waveform length placeholder */
+    dspmem_chunk_write(ch, 8, 0);         /* Repeat */
+    dspmem_chunk_write(ch, 12, 0);        /* Wait time between repeats */
+    dspmem_chunk_write(ch, 8, 0x00);      /* nsections placeholder */
+
+    for (auto &e : composite) {
+        switch (e.getTag()) {
+            case PrimitivePwle::active: {
+                auto active = e.get<PrimitivePwle::active>();
+                if (active.duration < 0 ||
+                    active.duration > COMPOSE_PWLE_PRIMITIVE_DURATION_MAX_MS) {
+                    return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
+                }
+                if (active.startAmplitude < PWLE_LEVEL_MIN ||
+                    active.startAmplitude > PWLE_LEVEL_MAX ||
+                    active.endAmplitude < PWLE_LEVEL_MIN || active.endAmplitude > PWLE_LEVEL_MAX) {
+                    return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
+                }
+                if (active.startAmplitude > CS40L26_PWLE_LEVEL_MAX) {
+                    active.startAmplitude = CS40L26_PWLE_LEVEL_MAX;
+                }
+                if (active.endAmplitude > CS40L26_PWLE_LEVEL_MAX) {
+                    active.endAmplitude = CS40L26_PWLE_LEVEL_MAX;
+                }
+
+                if (active.startFrequency < PWLE_FREQUENCY_MIN_HZ ||
+                    active.startFrequency > PWLE_FREQUENCY_MAX_HZ ||
+                    active.endFrequency < PWLE_FREQUENCY_MIN_HZ ||
+                    active.endFrequency > PWLE_FREQUENCY_MAX_HZ) {
+                    return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
+                }
+
+                if (!((active.startAmplitude == prevEndAmplitude) &&
+                      (active.startFrequency == prevEndFrequency))) {
+                    if (constructActiveSegment(ch, 0, active.startAmplitude, active.startFrequency,
+                                               false) < 0) {
+                        return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
+                    }
+                    incrementIndex(&segmentIdx);
+                }
+
+                if (active.startFrequency != active.endFrequency) {
+                    chirp = true;
+                }
+                if (constructActiveSegment(ch, active.duration, active.endAmplitude,
+                                           active.endFrequency, chirp) < 0) {
+                    return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
+                }
+                incrementIndex(&segmentIdx);
+
+                prevEndAmplitude = active.endAmplitude;
+                prevEndFrequency = active.endFrequency;
+                totalDuration += active.duration;
+                chirp = false;
+                break;
+            }
+            case PrimitivePwle::braking: {
+                auto braking = e.get<PrimitivePwle::braking>();
+                if (braking.braking > Braking::CLAB) {
+                    return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
+                } else if (!isClabSupported && (braking.braking == Braking::CLAB)) {
+                    return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
+                }
+
+                if (braking.duration > COMPOSE_PWLE_PRIMITIVE_DURATION_MAX_MS) {
+                    return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
+                }
+
+                if (constructBrakingSegment(ch, 0, braking.braking) < 0) {
+                    return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
+                }
+                incrementIndex(&segmentIdx);
+
+                if (constructBrakingSegment(ch, braking.duration, braking.braking) < 0) {
+                    return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
+                }
+                incrementIndex(&segmentIdx);
+
+                resetPreviousEndAmplitudeEndFrequency(&prevEndAmplitude, &prevEndFrequency);
+                totalDuration += braking.duration;
+                break;
+            }
+        }
+
+        if (segmentIdx > COMPOSE_PWLE_SIZE_MAX_DEFAULT) {
+            ALOGE("Too many PrimitivePwle section!");
+            return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
+        }
+    }
+    dspmem_chunk_flush(ch);
+
+    /* Update wlength */
+    totalDuration += MAX_COLD_START_LATENCY_MS;
+    if (totalDuration > 0x7FFFF) {
+        ALOGE("Total duration is too long (%d)!", totalDuration);
+        return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
+    }
+    updateWLength(ch, totalDuration);
+
+    /* Update nsections */
+    updateNSection(ch, segmentIdx);
+
+    return performEffect(WAVEFORM_MAX_INDEX /*ignored*/, VOLTAGE_SCALE_MAX /*ignored*/, ch,
+                         callback);
+}
+
+bool Vibrator::isUnderExternalControl() {
+    return mIsUnderExternalControl;
+}
+
+binder_status_t Vibrator::dump(int fd, const char **args, uint32_t numArgs) {
+    if (fd < 0) {
+        ALOGE("Called debug() with invalid fd.");
+        return STATUS_OK;
+    }
+
+    (void)args;
+    (void)numArgs;
+
+    dprintf(fd, "AIDL:\n");
+
+    dprintf(fd, "  F0 Offset: %" PRIu32 "\n", mF0Offset);
+
+    dprintf(fd, "  Voltage Levels:\n");
+    dprintf(fd, "    Tick Effect Min: %" PRIu32 " Max: %" PRIu32 "\n", mTickEffectVol[0],
+            mTickEffectVol[1]);
+    dprintf(fd, "    Click Effect Min: %" PRIu32 " Max: %" PRIu32 "\n", mClickEffectVol[0],
+            mClickEffectVol[1]);
+    dprintf(fd, "    Long Effect Min: %" PRIu32 " Max: %" PRIu32 "\n", mLongEffectVol[0],
+            mLongEffectVol[1]);
+
+    dprintf(fd, "  FF effect:\n");
+    dprintf(fd, "    Physical waveform:\n");
+    dprintf(fd, "\tId\tIndex\tt   ->\tt'\n");
+    for (uint8_t effectId = 0; effectId < WAVEFORM_MAX_PHYSICAL_INDEX; effectId++) {
+        dprintf(fd, "\t%d\t%d\t%d\t%d\n", mFfEffects[effectId].id,
+                mFfEffects[effectId].u.periodic.custom_data[1], mEffectDurations[effectId],
+                mFfEffects[effectId].replay.length);
+    }
+    dprintf(fd, "    OWT waveform:\n");
+    dprintf(fd, "\tId\tBytes\tData\n");
+    for (uint8_t effectId = WAVEFORM_MAX_PHYSICAL_INDEX; effectId < WAVEFORM_MAX_INDEX;
+         effectId++) {
+        uint32_t numBytes = mFfEffects[effectId].u.periodic.custom_len * 2;
+        std::stringstream ss;
+        ss << " ";
+        for (int i = 0; i < numBytes; i++) {
+            ss << std::uppercase << std::setfill('0') << std::setw(2) << std::hex
+               << (uint16_t)(*(
+                          reinterpret_cast<uint8_t *>(mFfEffects[effectId].u.periodic.custom_data) +
+                          i))
+               << " ";
+        }
+        dprintf(fd, "\t%d\t%d\t{%s}\n", mFfEffects[effectId].id, numBytes, ss.str().c_str());
+    }
+
+    dprintf(fd, "\n");
+    dprintf(fd, "\n");
+
+    mHwApi->debug(fd);
+
+    dprintf(fd, "\n");
+
+    mHwCal->debug(fd);
+
+    fsync(fd);
+    return STATUS_OK;
+}
+
+bool Vibrator::hasHapticAlsaDevice() {
+    // We need to call findHapticAlsaDevice once only. Calling in the
+    // constructor is too early in the boot process and the pcm file contents
+    // are empty. Hence we make the call here once only right before we need to.
+    if (!mConfigHapticAlsaDeviceDone) {
+        if (mHwApi->getHapticAlsaDevice(&mCard, &mDevice)) {
+            mHasHapticAlsaDevice = true;
+            mConfigHapticAlsaDeviceDone = true;
+        } else {
+            ALOGE("Haptic ALSA device not supported");
+        }
+    } else {
+        ALOGD("Haptic ALSA device configuration done.");
+    }
+    return mHasHapticAlsaDevice;
+}
+
+ndk::ScopedAStatus Vibrator::getSimpleDetails(Effect effect, EffectStrength strength,
+                                              uint32_t *outEffectIndex, uint32_t *outTimeMs,
+                                              uint32_t *outVolLevel) {
+    uint32_t effectIndex;
+    uint32_t timeMs;
+    float intensity;
+    uint32_t volLevel;
+    switch (strength) {
+        case EffectStrength::LIGHT:
+            intensity = 0.5f;
+            break;
+        case EffectStrength::MEDIUM:
+            intensity = 0.7f;
+            break;
+        case EffectStrength::STRONG:
+            intensity = 1.0f;
+            break;
+        default:
+            return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION);
+    }
+
+    switch (effect) {
+        case Effect::TEXTURE_TICK:
+            effectIndex = WAVEFORM_LIGHT_TICK_INDEX;
+            intensity *= 0.5f;
+            break;
+        case Effect::TICK:
+            effectIndex = WAVEFORM_CLICK_INDEX;
+            intensity *= 0.5f;
+            break;
+        case Effect::CLICK:
+            effectIndex = WAVEFORM_CLICK_INDEX;
+            intensity *= 0.7f;
+            break;
+        case Effect::HEAVY_CLICK:
+            effectIndex = WAVEFORM_CLICK_INDEX;
+            intensity *= 1.0f;
+            break;
+        default:
+            return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION);
+    }
+
+    volLevel = intensityToVolLevel(intensity, effectIndex);
+    timeMs = mEffectDurations[effectIndex] + MAX_COLD_START_LATENCY_MS;
+
+    *outEffectIndex = effectIndex;
+    *outTimeMs = timeMs;
+    *outVolLevel = volLevel;
+    return ndk::ScopedAStatus::ok();
+}
+
+ndk::ScopedAStatus Vibrator::getCompoundDetails(Effect effect, EffectStrength strength,
+                                                uint32_t *outTimeMs, dspmem_chunk *outCh) {
+    ndk::ScopedAStatus status;
+    uint32_t timeMs = 0;
+    uint32_t thisEffectIndex;
+    uint32_t thisTimeMs;
+    uint32_t thisVolLevel;
+    switch (effect) {
+        case Effect::DOUBLE_CLICK:
+            dspmem_chunk_write(outCh, 8, 0); /* Padding */
+            dspmem_chunk_write(outCh, 8, 2); /* nsections */
+            dspmem_chunk_write(outCh, 8, 0); /* repeat */
+
+            status = getSimpleDetails(Effect::CLICK, strength, &thisEffectIndex, &thisTimeMs,
+                                      &thisVolLevel);
+            if (!status.isOk()) {
+                return status;
+            }
+            timeMs += thisTimeMs;
+
+            dspmem_chunk_write(outCh, 8, (uint8_t)(0xFF & thisVolLevel));    /* amplitude */
+            dspmem_chunk_write(outCh, 8, (uint8_t)(0xFF & thisEffectIndex)); /* index */
+            dspmem_chunk_write(outCh, 8, 0);                                 /* repeat */
+            dspmem_chunk_write(outCh, 8, 0);                                 /* flags */
+            dspmem_chunk_write(outCh, 16,
+                               (uint16_t)(0xFFFF & WAVEFORM_DOUBLE_CLICK_SILENCE_MS)); /* delay */
+
+            timeMs += WAVEFORM_DOUBLE_CLICK_SILENCE_MS + MAX_PAUSE_TIMING_ERROR_MS;
+
+            status = getSimpleDetails(Effect::HEAVY_CLICK, strength, &thisEffectIndex, &thisTimeMs,
+                                      &thisVolLevel);
+            if (!status.isOk()) {
+                return status;
+            }
+            timeMs += thisTimeMs;
+
+            dspmem_chunk_write(outCh, 8, (uint8_t)(0xFF & thisVolLevel));    /* amplitude */
+            dspmem_chunk_write(outCh, 8, (uint8_t)(0xFF & thisEffectIndex)); /* index */
+            dspmem_chunk_write(outCh, 8, 0);                                 /* repeat */
+            dspmem_chunk_write(outCh, 8, 0);                                 /* flags */
+            dspmem_chunk_write(outCh, 16, 0);                                /* delay */
+            dspmem_chunk_flush(outCh);
+
+            break;
+        default:
+            return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION);
+    }
+
+    *outTimeMs = timeMs;
+
+    return ndk::ScopedAStatus::ok();
+}
+
+ndk::ScopedAStatus Vibrator::getPrimitiveDetails(CompositePrimitive primitive,
+                                                 uint32_t *outEffectIndex) {
+    uint32_t effectIndex;
+    uint32_t primitiveBit = 1 << int32_t(primitive);
+    if ((primitiveBit & mSupportedPrimitivesBits) == 0x0) {
+        return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION);
+    }
+
+    switch (primitive) {
+        case CompositePrimitive::NOOP:
+            return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
+        case CompositePrimitive::CLICK:
+            effectIndex = WAVEFORM_CLICK_INDEX;
+            break;
+        case CompositePrimitive::THUD:
+            effectIndex = WAVEFORM_THUD_INDEX;
+            break;
+        case CompositePrimitive::SPIN:
+            effectIndex = WAVEFORM_SPIN_INDEX;
+            break;
+        case CompositePrimitive::QUICK_RISE:
+            effectIndex = WAVEFORM_QUICK_RISE_INDEX;
+            break;
+        case CompositePrimitive::SLOW_RISE:
+            effectIndex = WAVEFORM_SLOW_RISE_INDEX;
+            break;
+        case CompositePrimitive::QUICK_FALL:
+            effectIndex = WAVEFORM_QUICK_FALL_INDEX;
+            break;
+        case CompositePrimitive::LIGHT_TICK:
+            effectIndex = WAVEFORM_LIGHT_TICK_INDEX;
+            break;
+        case CompositePrimitive::LOW_TICK:
+            effectIndex = WAVEFORM_LOW_TICK_INDEX;
+            break;
+        default:
+            return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION);
+    }
+
+    *outEffectIndex = effectIndex;
+
+    return ndk::ScopedAStatus::ok();
+}
+
+ndk::ScopedAStatus Vibrator::performEffect(Effect effect, EffectStrength strength,
+                                           const std::shared_ptr<IVibratorCallback> &callback,
+                                           int32_t *outTimeMs) {
+    ndk::ScopedAStatus status;
+    uint32_t effectIndex;
+    uint32_t timeMs = 0;
+    uint32_t volLevel;
+    dspmem_chunk *ch = nullptr;
+    switch (effect) {
+        case Effect::TEXTURE_TICK:
+            // fall-through
+        case Effect::TICK:
+            // fall-through
+        case Effect::CLICK:
+            // fall-through
+        case Effect::HEAVY_CLICK:
+            status = getSimpleDetails(effect, strength, &effectIndex, &timeMs, &volLevel);
+            break;
+        case Effect::DOUBLE_CLICK:
+            ch = dspmem_chunk_create(new uint8_t[FF_CUSTOM_DATA_LEN_MAX_COMP]{0x00},
+                                     FF_CUSTOM_DATA_LEN_MAX_COMP);
+            status = getCompoundDetails(effect, strength, &timeMs, ch);
+            volLevel = VOLTAGE_SCALE_MAX;
+            break;
+        default:
+            status = ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION);
+            break;
+    }
+    if (!status.isOk()) {
+        goto exit;
+    }
+
+    status = performEffect(effectIndex, volLevel, ch, callback);
+
+exit:
+    *outTimeMs = timeMs;
+    return status;
+}
+
+ndk::ScopedAStatus Vibrator::performEffect(uint32_t effectIndex, uint32_t volLevel,
+                                           dspmem_chunk *ch,
+                                           const std::shared_ptr<IVibratorCallback> &callback) {
+    setEffectAmplitude(volLevel, VOLTAGE_SCALE_MAX);
+
+    return on(MAX_TIME_MS, effectIndex, ch, callback);
+}
+
+void Vibrator::waitForComplete(std::shared_ptr<IVibratorCallback> &&callback) {
+    if (!mHwApi->pollVibeState(VIBE_STATE_HAPTIC, POLLING_TIMEOUT)) {
+        ALOGW("Failed to get state \"Haptic\"");
+    }
+    mHwApi->pollVibeState(VIBE_STATE_STOPPED);
+
+    const std::scoped_lock<std::mutex> lock(mActiveId_mutex);
+    if ((mActiveId >= WAVEFORM_MAX_PHYSICAL_INDEX) &&
+        (!mHwApi->eraseOwtEffect(mInputFd, mActiveId, &mFfEffects))) {
+        ALOGE("Failed to clean up the composed effect %d", mActiveId);
+    }
+    mActiveId = -1;
+
+    if (callback) {
+        auto ret = callback->onComplete();
+        if (!ret.isOk()) {
+            ALOGE("Failed completion callback: %d", ret.getExceptionCode());
+        }
+    }
+}
+
+uint32_t Vibrator::intensityToVolLevel(float intensity, uint32_t effectIndex) {
+    uint32_t volLevel;
+    auto calc = [](float intst, std::array<uint32_t, 2> v) -> uint32_t {
+        return std::lround(intst * (v[1] - v[0])) + v[0];
+    };
+
+    switch (effectIndex) {
+        case WAVEFORM_LIGHT_TICK_INDEX:
+            volLevel = calc(intensity, mTickEffectVol);
+            break;
+        case WAVEFORM_QUICK_RISE_INDEX:
+            // fall-through
+        case WAVEFORM_QUICK_FALL_INDEX:
+            volLevel = calc(intensity, mLongEffectVol);
+            break;
+        case WAVEFORM_CLICK_INDEX:
+            // fall-through
+        case WAVEFORM_THUD_INDEX:
+            // fall-through
+        case WAVEFORM_SPIN_INDEX:
+            // fall-through
+        case WAVEFORM_SLOW_RISE_INDEX:
+            // fall-through
+        default:
+            volLevel = calc(intensity, mClickEffectVol);
+            break;
+    }
+    return volLevel;
+}
+
+}  // namespace vibrator
+}  // namespace hardware
+}  // namespace android
+}  // namespace aidl

+ 211 - 0
vibrator/cs40l26/Vibrator.h

@@ -0,0 +1,211 @@
+/*
+ * Copyright (C) 2021 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/BnVibrator.h>
+#include <android-base/unique_fd.h>
+#include <linux/input.h>
+#include <tinyalsa/asoundlib.h>
+
+#include <array>
+#include <fstream>
+#include <future>
+
+namespace aidl {
+namespace android {
+namespace hardware {
+namespace vibrator {
+
+class Vibrator : public BnVibrator {
+  public:
+    // APIs for interfacing with the kernel driver.
+    class HwApi {
+      public:
+        virtual ~HwApi() = default;
+        // Stores the LRA resonant frequency to be used for PWLE playback
+        // and click compensation.
+        virtual bool setF0(std::string value) = 0;
+        // Stores the frequency offset for long vibrations.
+        virtual bool setF0Offset(uint32_t value) = 0;
+        // Stores the LRA series resistance to be used for click
+        // compensation.
+        virtual bool setRedc(std::string value) = 0;
+        // Stores the LRA Q factor to be used for Q-dependent waveform
+        // selection.
+        virtual bool setQ(std::string value) = 0;
+        // Reports the number of effect waveforms loaded in firmware.
+        virtual bool getEffectCount(uint32_t *value) = 0;
+        // Blocks until timeout or vibrator reaches desired state
+        // (2 = ASP enabled, 1 = haptic enabled, 0 = disabled).
+        virtual bool pollVibeState(uint32_t value, int32_t timeoutMs = -1) = 0;
+        // Reports whether getOwtFreeSpace() is supported.
+        virtual bool hasOwtFreeSpace() = 0;
+        // Reports the available OWT bytes.
+        virtual bool getOwtFreeSpace(uint32_t *value) = 0;
+        // Enables/Disables F0 compensation enable status
+        virtual bool setF0CompEnable(bool value) = 0;
+        // Enables/Disables Redc compensation enable status
+        virtual bool setRedcCompEnable(bool value) = 0;
+        // Stores the minumun delay time between playback and stop effects.
+        virtual bool setMinOnOffInterval(uint32_t value) = 0;
+        // Indicates the number of 0.125-dB steps of attenuation to apply to
+        // waveforms triggered in response to vibration calls from the
+        // Android vibrator HAL.
+        virtual bool setFFGain(int fd, uint16_t value) = 0;
+        // Create/modify custom effects for all physical waveforms.
+        virtual bool setFFEffect(int fd, struct ff_effect *effect, uint16_t timeoutMs) = 0;
+        // Activates/deactivates the effect index after setFFGain() and setFFEffect().
+        virtual bool setFFPlay(int fd, int8_t index, bool value) = 0;
+        // Get the Alsa device for the audio coupled haptics effect
+        virtual bool getHapticAlsaDevice(int *card, int *device) = 0;
+        // Set haptics PCM amplifier before triggering audio haptics feature
+        virtual bool setHapticPcmAmp(struct pcm **haptic_pcm, bool enable, int card,
+                                     int device) = 0;
+        // Set OWT waveform for compose or compose PWLE request
+        virtual bool uploadOwtEffect(int fd, uint8_t *owtData, uint32_t numBytes,
+                                     struct ff_effect *effect, uint32_t *outEffectIndex,
+                                     int *status) = 0;
+        // Erase OWT waveform
+        virtual bool eraseOwtEffect(int fd, int8_t effectIndex, std::vector<ff_effect> *effect) = 0;
+        // Emit diagnostic information to the given file.
+        virtual void debug(int fd) = 0;
+    };
+
+    // APIs for obtaining calibration/configuration data from persistent memory.
+    class HwCal {
+      public:
+        virtual ~HwCal() = default;
+        // Obtain the calibration version
+        virtual bool getVersion(uint32_t *value) = 0;
+        // Obtains the LRA resonant frequency to be used for PWLE playback
+        // and click compensation.
+        virtual bool getF0(std::string *value) = 0;
+        // Obtains the LRA series resistance to be used for click
+        // compensation.
+        virtual bool getRedc(std::string *value) = 0;
+        // Obtains the LRA Q factor to be used for Q-dependent waveform
+        // selection.
+        virtual bool getQ(std::string *value) = 0;
+        // Obtains frequency shift for long vibrations.
+        virtual bool getLongFrequencyShift(int32_t *value) = 0;
+        // Obtains the v0/v1(min/max) voltage levels to be applied for
+        // tick/click/long in units of 1%.
+        virtual bool getTickVolLevels(std::array<uint32_t, 2> *value) = 0;
+        virtual bool getClickVolLevels(std::array<uint32_t, 2> *value) = 0;
+        virtual bool getLongVolLevels(std::array<uint32_t, 2> *value) = 0;
+        // Checks if the chirp feature is enabled.
+        virtual bool isChirpEnabled() = 0;
+        // Obtains the supported primitive effects.
+        virtual bool getSupportedPrimitives(uint32_t *value) = 0;
+        // Checks if the f0 compensation feature needs to be enabled.
+        virtual bool isF0CompEnabled() = 0;
+        // Checks if the redc compensation feature needs to be enabled.
+        virtual bool isRedcCompEnabled() = 0;
+        // Emit diagnostic information to the given file.
+        virtual void debug(int fd) = 0;
+    };
+
+  public:
+    Vibrator(std::unique_ptr<HwApi> hwapi, std::unique_ptr<HwCal> hwcal);
+
+    ndk::ScopedAStatus getCapabilities(int32_t *_aidl_return) override;
+    ndk::ScopedAStatus off() override;
+    ndk::ScopedAStatus on(int32_t timeoutMs,
+                          const std::shared_ptr<IVibratorCallback> &callback) override;
+    ndk::ScopedAStatus perform(Effect effect, EffectStrength strength,
+                               const std::shared_ptr<IVibratorCallback> &callback,
+                               int32_t *_aidl_return) override;
+    ndk::ScopedAStatus getSupportedEffects(std::vector<Effect> *_aidl_return) override;
+    ndk::ScopedAStatus setAmplitude(float amplitude) override;
+    ndk::ScopedAStatus setExternalControl(bool enabled) override;
+    ndk::ScopedAStatus getCompositionDelayMax(int32_t *maxDelayMs);
+    ndk::ScopedAStatus getCompositionSizeMax(int32_t *maxSize);
+    ndk::ScopedAStatus getSupportedPrimitives(std::vector<CompositePrimitive> *supported) override;
+    ndk::ScopedAStatus getPrimitiveDuration(CompositePrimitive primitive,
+                                            int32_t *durationMs) override;
+    ndk::ScopedAStatus compose(const std::vector<CompositeEffect> &composite,
+                               const std::shared_ptr<IVibratorCallback> &callback) override;
+    ndk::ScopedAStatus getSupportedAlwaysOnEffects(std::vector<Effect> *_aidl_return) override;
+    ndk::ScopedAStatus alwaysOnEnable(int32_t id, Effect effect, EffectStrength strength) override;
+    ndk::ScopedAStatus alwaysOnDisable(int32_t id) override;
+    ndk::ScopedAStatus getResonantFrequency(float *resonantFreqHz) override;
+    ndk::ScopedAStatus getQFactor(float *qFactor) override;
+    ndk::ScopedAStatus getFrequencyResolution(float *freqResolutionHz) override;
+    ndk::ScopedAStatus getFrequencyMinimum(float *freqMinimumHz) override;
+    ndk::ScopedAStatus getBandwidthAmplitudeMap(std::vector<float> *_aidl_return) override;
+    ndk::ScopedAStatus getPwlePrimitiveDurationMax(int32_t *durationMs) override;
+    ndk::ScopedAStatus getPwleCompositionSizeMax(int32_t *maxSize) override;
+    ndk::ScopedAStatus getSupportedBraking(std::vector<Braking> *supported) override;
+    ndk::ScopedAStatus composePwle(const std::vector<PrimitivePwle> &composite,
+                                   const std::shared_ptr<IVibratorCallback> &callback) override;
+
+    binder_status_t dump(int fd, const char **args, uint32_t numArgs) override;
+
+  private:
+    ndk::ScopedAStatus on(uint32_t timeoutMs, uint32_t effectIndex, struct dspmem_chunk *ch,
+                          const std::shared_ptr<IVibratorCallback> &callback);
+    // set 'amplitude' based on an arbitrary scale determined by 'maximum'
+    ndk::ScopedAStatus setEffectAmplitude(float amplitude, float maximum);
+    ndk::ScopedAStatus setGlobalAmplitude(bool set);
+    // 'simple' effects are those precompiled and loaded into the controller
+    ndk::ScopedAStatus getSimpleDetails(Effect effect, EffectStrength strength,
+                                        uint32_t *outEffectIndex, uint32_t *outTimeMs,
+                                        uint32_t *outVolLevel);
+    // 'compound' effects are those composed by stringing multiple 'simple' effects
+    ndk::ScopedAStatus getCompoundDetails(Effect effect, EffectStrength strength,
+                                          uint32_t *outTimeMs, struct dspmem_chunk *outCh);
+    ndk::ScopedAStatus getPrimitiveDetails(CompositePrimitive primitive, uint32_t *outEffectIndex);
+    ndk::ScopedAStatus performEffect(Effect effect, EffectStrength strength,
+                                     const std::shared_ptr<IVibratorCallback> &callback,
+                                     int32_t *outTimeMs);
+    ndk::ScopedAStatus performEffect(uint32_t effectIndex, uint32_t volLevel,
+                                     struct dspmem_chunk *ch,
+                                     const std::shared_ptr<IVibratorCallback> &callback);
+    ndk::ScopedAStatus setPwle(const std::string &pwleQueue);
+    bool isUnderExternalControl();
+    void waitForComplete(std::shared_ptr<IVibratorCallback> &&callback);
+    uint32_t intensityToVolLevel(float intensity, uint32_t effectIndex);
+    bool findHapticAlsaDevice(int *card, int *device);
+    bool hasHapticAlsaDevice();
+    bool enableHapticPcmAmp(struct pcm **haptic_pcm, bool enable, int card, int device);
+
+    std::unique_ptr<HwApi> mHwApi;
+    std::unique_ptr<HwCal> mHwCal;
+    uint32_t mF0Offset;
+    std::array<uint32_t, 2> mTickEffectVol;
+    std::array<uint32_t, 2> mClickEffectVol;
+    std::array<uint32_t, 2> mLongEffectVol;
+    std::vector<ff_effect> mFfEffects;
+    std::vector<uint32_t> mEffectDurations;
+    std::future<void> mAsyncHandle;
+    ::android::base::unique_fd mInputFd;
+    int8_t mActiveId{-1};
+    struct pcm *mHapticPcm;
+    int mCard;
+    int mDevice;
+    bool mHasHapticAlsaDevice{false};
+    bool mIsUnderExternalControl;
+    float mLongEffectScale = 1.0;
+    bool mIsChirpEnabled;
+    uint32_t mSupportedPrimitivesBits = 0x0;
+    std::vector<CompositePrimitive> mSupportedPrimitives;
+    bool mConfigHapticAlsaDeviceDone{false};
+};
+
+}  // namespace vibrator
+}  // namespace hardware
+}  // namespace android
+}  // namespace aidl

+ 47 - 0
vibrator/cs40l26/android.hardware.vibrator-service.cs40l26.rc

@@ -0,0 +1,47 @@
+on property:vendor.all.modules.ready=1
+    wait /sys/bus/i2c/devices/i2c-cs40l26a/calibration/redc_cal_time_ms
+
+    mkdir /mnt/vendor/persist/haptics 0770 system system
+    chmod 770 /mnt/vendor/persist/haptics
+    chmod 440 /mnt/vendor/persist/haptics/cs40l26.cal
+    chown system system /mnt/vendor/persist/haptics
+    chown system system /mnt/vendor/persist/haptics/cs40l26.cal
+
+    chown system system /sys/bus/i2c/devices/i2c-cs40l26a/calibration/f0_stored
+    chown system system /sys/bus/i2c/devices/i2c-cs40l26a/calibration/q_stored
+    chown system system /sys/bus/i2c/devices/i2c-cs40l26a/calibration/redc_stored
+    chown system system /sys/bus/i2c/devices/i2c-cs40l26a/default/vibe_state
+    chown system system /sys/bus/i2c/devices/i2c-cs40l26a/default/num_waves
+    chown system system /sys/bus/i2c/devices/i2c-cs40l26a/default/f0_offset
+    chown system system /sys/bus/i2c/devices/i2c-cs40l26a/default/owt_free_space
+    chown system system /sys/bus/i2c/devices/i2c-cs40l26a/default/f0_comp_enable
+    chown system system /sys/bus/i2c/devices/i2c-cs40l26a/default/redc_comp_enable
+    chown system system /sys/bus/i2c/devices/i2c-cs40l26a/default/delay_before_stop_playback_us
+
+    enable vendor.vibrator.cs40l26
+
+service vendor.vibrator.cs40l26 /vendor/bin/hw/android.hardware.vibrator-service.cs40l26
+    class hal
+    user system
+    group system input
+
+    setenv INPUT_EVENT_NAME cs40l26_input
+    setenv INPUT_EVENT_PATH /dev/input/event*
+    setenv PROPERTY_PREFIX ro.vendor.vibrator.hal.
+    setenv CALIBRATION_FILEPATH /mnt/vendor/persist/haptics/cs40l26.cal
+
+    setenv HWAPI_PATH_PREFIX /sys/bus/i2c/devices/i2c-cs40l26a/
+    setenv HWAPI_DEBUG_PATHS "
+        calibration/f0_stored
+        calibration/redc_stored
+        calibration/q_stored
+        default/vibe_state
+        default/num_waves
+        default/f0_offset
+        default/owt_free_space
+        default/f0_comp_enable
+        default/redc_comp_enable
+        default/delay_before_stop_playback_us
+        "
+
+    disabled

+ 7 - 0
vibrator/cs40l26/android.hardware.vibrator-service.cs40l26.xml

@@ -0,0 +1,7 @@
+<manifest version="1.0" type="device">
+    <hal format="aidl">
+        <name>android.hardware.vibrator</name>
+        <version>2</version>
+        <fqname>IVibrator/default</fqname>
+    </hal>
+</manifest>

+ 6 - 0
vibrator/cs40l26/device.mk

@@ -0,0 +1,6 @@
+PRODUCT_PACKAGES += \
+    android.hardware.vibrator-service.cs40l26
+
+BOARD_SEPOLICY_DIRS += \
+    hardware/google/pixel-sepolicy/vibrator/common \
+    hardware/google/pixel-sepolicy/vibrator/cs40l26

+ 55 - 0
vibrator/cs40l26/service.cpp

@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2021 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 <android/binder_manager.h>
+#include <android/binder_process.h>
+#include <binder/IServiceManager.h>
+#include <binder/ProcessState.h>
+#include <log/log.h>
+
+#include "Hardware.h"
+#include "Vibrator.h"
+
+using ::aidl::android::hardware::vibrator::HwApi;
+using ::aidl::android::hardware::vibrator::HwCal;
+using ::aidl::android::hardware::vibrator::Vibrator;
+using ::android::defaultServiceManager;
+using ::android::ProcessState;
+using ::android::sp;
+using ::android::String16;
+
+#if !defined(VIBRATOR_NAME)
+#define VIBRATOR_NAME "default"
+#endif
+
+int main() {
+    auto svc = ndk::SharedRefBase::make<Vibrator>(std::make_unique<HwApi>(),
+                                                  std::make_unique<HwCal>());
+    const auto svcName = std::string() + svc->descriptor + "/" + VIBRATOR_NAME;
+
+    ProcessState::initWithDriver("/dev/vndbinder");
+
+    auto svcBinder = svc->asBinder();
+    binder_status_t status = AServiceManager_addService(svcBinder.get(), svcName.c_str());
+    LOG_ALWAYS_FATAL_IF(status != STATUS_OK);
+
+    ProcessState::self()->setThreadPoolMaxThreadCount(1);
+    ProcessState::self()->startThreadPool();
+
+    ABinderProcess_setThreadPoolMaxThreadCount(0);
+    ABinderProcess_joinThreadPool();
+
+    return EXIT_FAILURE;  // should not reach
+}

+ 35 - 0
vibrator/cs40l26/tests/Android.bp

@@ -0,0 +1,35 @@
+//
+// Copyright (C) 2022 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 {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+cc_test {
+    name: "VibratorHalCs40l26TestSuite",
+    defaults: ["VibratorHalCs40l26TestDefaults"],
+    srcs: [
+        "test-hwcal.cpp",
+	"test-hwapi.cpp",
+	"test-vibrator.cpp",
+    ],
+    static_libs: [
+        "libc++fs",
+        "libgmock",
+    ],
+    shared_libs: [
+        "libbase",
+    ],
+}

+ 80 - 0
vibrator/cs40l26/tests/mocks.h

@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#ifndef ANDROID_HARDWARE_VIBRATOR_TEST_MOCKS_H
+#define ANDROID_HARDWARE_VIBRATOR_TEST_MOCKS_H
+
+#include <aidl/android/hardware/vibrator/BnVibratorCallback.h>
+
+#include "Vibrator.h"
+
+class MockApi : public ::aidl::android::hardware::vibrator::Vibrator::HwApi {
+  public:
+    MOCK_METHOD0(destructor, void());
+    MOCK_METHOD1(setF0, bool(std::string value));
+    MOCK_METHOD1(setF0Offset, bool(uint32_t value));
+    MOCK_METHOD1(setRedc, bool(std::string value));
+    MOCK_METHOD1(setQ, bool(std::string value));
+    MOCK_METHOD1(getEffectCount, bool(uint32_t *value));
+    MOCK_METHOD2(pollVibeState, bool(uint32_t value, int32_t timeoutMs));
+    MOCK_METHOD0(hasOwtFreeSpace, bool());
+    MOCK_METHOD1(getOwtFreeSpace, bool(uint32_t *value));
+    MOCK_METHOD1(setF0CompEnable, bool(bool value));
+    MOCK_METHOD1(setRedcCompEnable, bool(bool value));
+    MOCK_METHOD1(setMinOnOffInterval, bool(uint32_t value));
+    MOCK_METHOD2(setFFGain, bool(int fd, uint16_t value));
+    MOCK_METHOD3(setFFEffect, bool(int fd, struct ff_effect *effect, uint16_t timeoutMs));
+    MOCK_METHOD3(setFFPlay, bool(int fd, int8_t index, bool value));
+    MOCK_METHOD2(getHapticAlsaDevice, bool(int *card, int *device));
+    MOCK_METHOD4(setHapticPcmAmp, bool(struct pcm **haptic_pcm, bool enable, int card, int device));
+    MOCK_METHOD6(uploadOwtEffect,
+                 bool(int fd, uint8_t *owtData, uint32_t numBytes, struct ff_effect *effect,
+                      uint32_t *outEffectIndex, int *status));
+    MOCK_METHOD3(eraseOwtEffect, bool(int fd, int8_t effectIndex, std::vector<ff_effect> *effect));
+    MOCK_METHOD1(debug, void(int fd));
+
+    ~MockApi() override { destructor(); };
+};
+
+class MockCal : public ::aidl::android::hardware::vibrator::Vibrator::HwCal {
+  public:
+    MOCK_METHOD0(destructor, void());
+    MOCK_METHOD1(getVersion, bool(uint32_t *value));
+    MOCK_METHOD1(getF0, bool(std::string &value));
+    MOCK_METHOD1(getRedc, bool(std::string &value));
+    MOCK_METHOD1(getQ, bool(std::string &value));
+    MOCK_METHOD1(getLongFrequencyShift, bool(int32_t *value));
+    MOCK_METHOD1(getTickVolLevels, bool(std::array<uint32_t, 2> *value));
+    MOCK_METHOD1(getClickVolLevels, bool(std::array<uint32_t, 2> *value));
+    MOCK_METHOD1(getLongVolLevels, bool(std::array<uint32_t, 2> *value));
+    MOCK_METHOD0(isChirpEnabled, bool());
+    MOCK_METHOD1(getSupportedPrimitives, bool(uint32_t *value));
+    MOCK_METHOD0(isF0CompEnabled, bool());
+    MOCK_METHOD0(isRedcCompEnabled, bool());
+    MOCK_METHOD1(debug, void(int fd));
+
+    ~MockCal() override { destructor(); };
+    // b/132668253: Workaround gMock Compilation Issue
+    bool getF0(std::string *value) { return getF0(*value); }
+    bool getRedc(std::string *value) { return getRedc(*value); }
+    bool getQ(std::string *value) { return getQ(*value); }
+};
+
+class MockVibratorCallback : public aidl::android::hardware::vibrator::BnVibratorCallback {
+  public:
+    MOCK_METHOD(ndk::ScopedAStatus, onComplete, ());
+};
+
+#endif  // ANDROID_HARDWARE_VIBRATOR_TEST_MOCKS_H

+ 288 - 0
vibrator/cs40l26/tests/test-hwapi.cpp

@@ -0,0 +1,288 @@
+/*
+ * Copyright (C) 2022 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 <android-base/file.h>
+#include <cutils/fs.h>
+#include <gtest/gtest.h>
+
+#include <cstdlib>
+#include <fstream>
+
+#include "Hardware.h"
+
+namespace aidl {
+namespace android {
+namespace hardware {
+namespace vibrator {
+
+using ::testing::Test;
+using ::testing::TestParamInfo;
+using ::testing::ValuesIn;
+using ::testing::WithParamInterface;
+
+class HwApiTest : public Test {
+  private:
+    static constexpr const char *FILE_NAMES[]{
+            "calibration/f0_stored",
+            "default/f0_offset",
+            "calibration/redc_stored",
+            "calibration/q_stored",
+            "default/f0_comp_enable",
+            "default/redc_comp_enable",
+            "default/owt_free_space",
+            "default/num_waves",
+            "default/delay_before_stop_playback_us",
+    };
+
+  public:
+    void SetUp() override {
+        std::string prefix;
+        for (auto n : FILE_NAMES) {
+            auto name = std::filesystem::path(n);
+            auto path = std::filesystem::path(mFilesDir.path) / name;
+            fs_mkdirs(path.c_str(), S_IRWXU);
+            std::ofstream touch{path};
+            mFileMap[name] = path;
+        }
+        prefix = std::filesystem::path(mFilesDir.path) / "";
+        setenv("HWAPI_PATH_PREFIX", prefix.c_str(), true);
+        mHwApi = std::make_unique<HwApi>();
+
+        for (auto n : FILE_NAMES) {
+            auto name = std::filesystem::path(n);
+            auto path = std::filesystem::path(mEmptyDir.path) / name;
+        }
+        prefix = std::filesystem::path(mEmptyDir.path) / "";
+        setenv("HWAPI_PATH_PREFIX", prefix.c_str(), true);
+        mNoApi = std::make_unique<HwApi>();
+    }
+
+    void TearDown() override { verifyContents(); }
+
+    static auto ParamNameFixup(std::string str) {
+        std::replace(str.begin(), str.end(), '/', '_');
+        return str;
+    }
+
+  protected:
+    // Set expected file content for a test.
+    template <typename T>
+    void expectContent(const std::string &name, const T &value) {
+        mExpectedContent[name] << value << std::endl;
+    }
+
+    // Set actual file content for an input test.
+    template <typename T>
+    void updateContent(const std::string &name, const T &value) {
+        std::ofstream(mFileMap[name]) << value << std::endl;
+    }
+
+    template <typename T>
+    void expectAndUpdateContent(const std::string &name, const T &value) {
+        expectContent(name, value);
+        updateContent(name, value);
+    }
+
+    // Compare all file contents against expected contents.
+    void verifyContents() {
+        for (auto &a : mFileMap) {
+            std::ifstream file{a.second};
+            std::string expect = mExpectedContent[a.first].str();
+            std::string actual = std::string(std::istreambuf_iterator<char>(file),
+                                             std::istreambuf_iterator<char>());
+            EXPECT_EQ(expect, actual) << a.first;
+        }
+    }
+
+  protected:
+    std::unique_ptr<Vibrator::HwApi> mHwApi;
+    std::unique_ptr<Vibrator::HwApi> mNoApi;
+    std::map<std::string, std::string> mFileMap;
+    TemporaryDir mFilesDir;
+    TemporaryDir mEmptyDir;
+    std::map<std::string, std::stringstream> mExpectedContent;
+};
+
+template <typename T>
+class HwApiTypedTest : public HwApiTest,
+                       public WithParamInterface<std::tuple<std::string, std::function<T>>> {
+  public:
+    static auto PrintParam(const TestParamInfo<typename HwApiTypedTest::ParamType> &info) {
+        return ParamNameFixup(std::get<0>(info.param));
+    }
+    static auto MakeParam(std::string name, std::function<T> func) {
+        return std::make_tuple(name, func);
+    }
+};
+
+using HasTest = HwApiTypedTest<bool(Vibrator::HwApi &)>;
+
+TEST_P(HasTest, success_returnsTrue) {
+    auto param = GetParam();
+    auto func = std::get<1>(param);
+
+    EXPECT_TRUE(func(*mHwApi));
+}
+
+TEST_P(HasTest, success_returnsFalse) {
+    auto param = GetParam();
+    auto func = std::get<1>(param);
+
+    EXPECT_FALSE(func(*mNoApi));
+}
+
+INSTANTIATE_TEST_CASE_P(HwApiTests, HasTest,
+                        ValuesIn({
+                                HasTest::MakeParam("default/owt_free_space",
+                                                   &Vibrator::HwApi::hasOwtFreeSpace),
+                        }),
+                        HasTest::PrintParam);
+
+using GetUint32Test = HwApiTypedTest<bool(Vibrator::HwApi &, uint32_t *)>;
+
+TEST_P(GetUint32Test, success) {
+    auto param = GetParam();
+    auto name = std::get<0>(param);
+    auto func = std::get<1>(param);
+    uint32_t expect = std::rand();
+    uint32_t actual = ~expect;
+
+    expectAndUpdateContent(name, expect);
+
+    EXPECT_TRUE(func(*mHwApi, &actual));
+    EXPECT_EQ(expect, actual);
+}
+
+TEST_P(GetUint32Test, failure) {
+    auto param = GetParam();
+    auto func = std::get<1>(param);
+    uint32_t value;
+
+    EXPECT_FALSE(func(*mNoApi, &value));
+}
+
+INSTANTIATE_TEST_CASE_P(HwApiTests, GetUint32Test,
+                        ValuesIn({
+                                GetUint32Test::MakeParam("default/num_waves",
+                                                         &Vibrator::HwApi::getEffectCount),
+                                GetUint32Test::MakeParam("default/owt_free_space",
+                                                         &Vibrator::HwApi::getOwtFreeSpace),
+                        }),
+                        GetUint32Test::PrintParam);
+
+using SetBoolTest = HwApiTypedTest<bool(Vibrator::HwApi &, bool)>;
+
+TEST_P(SetBoolTest, success_returnsTrue) {
+    auto param = GetParam();
+    auto name = std::get<0>(param);
+    auto func = std::get<1>(param);
+
+    expectContent(name, "1");
+
+    EXPECT_TRUE(func(*mHwApi, true));
+}
+
+TEST_P(SetBoolTest, success_returnsFalse) {
+    auto param = GetParam();
+    auto name = std::get<0>(param);
+    auto func = std::get<1>(param);
+
+    expectContent(name, "0");
+
+    EXPECT_TRUE(func(*mHwApi, false));
+}
+
+TEST_P(SetBoolTest, failure) {
+    auto param = GetParam();
+    auto func = std::get<1>(param);
+
+    EXPECT_FALSE(func(*mNoApi, true));
+    EXPECT_FALSE(func(*mNoApi, false));
+}
+
+INSTANTIATE_TEST_CASE_P(HwApiTests, SetBoolTest,
+                        ValuesIn({
+                                SetBoolTest::MakeParam("default/f0_comp_enable",
+                                                       &Vibrator::HwApi::setF0CompEnable),
+                                SetBoolTest::MakeParam("default/redc_comp_enable",
+                                                       &Vibrator::HwApi::setRedcCompEnable),
+                        }),
+                        SetBoolTest::PrintParam);
+
+using SetUint32Test = HwApiTypedTest<bool(Vibrator::HwApi &, uint32_t)>;
+
+TEST_P(SetUint32Test, success) {
+    auto param = GetParam();
+    auto name = std::get<0>(param);
+    auto func = std::get<1>(param);
+    uint32_t value = std::rand();
+
+    expectContent(name, value);
+
+    EXPECT_TRUE(func(*mHwApi, value));
+}
+
+TEST_P(SetUint32Test, failure) {
+    auto param = GetParam();
+    auto func = std::get<1>(param);
+    uint32_t value = std::rand();
+
+    EXPECT_FALSE(func(*mNoApi, value));
+}
+
+INSTANTIATE_TEST_CASE_P(HwApiTests, SetUint32Test,
+                        ValuesIn({
+                                SetUint32Test::MakeParam("default/f0_offset",
+                                                         &Vibrator::HwApi::setF0Offset),
+                                SetUint32Test::MakeParam("default/delay_before_stop_playback_us",
+                                                         &Vibrator::HwApi::setMinOnOffInterval),
+                        }),
+                        SetUint32Test::PrintParam);
+
+using SetStringTest = HwApiTypedTest<bool(Vibrator::HwApi &, std::string)>;
+
+TEST_P(SetStringTest, success) {
+    auto param = GetParam();
+    auto name = std::get<0>(param);
+    auto func = std::get<1>(param);
+    std::string value = TemporaryFile().path;
+
+    expectContent(name, value);
+
+    EXPECT_TRUE(func(*mHwApi, value));
+}
+
+TEST_P(SetStringTest, failure) {
+    auto param = GetParam();
+    auto func = std::get<1>(param);
+    std::string value = TemporaryFile().path;
+
+    EXPECT_FALSE(func(*mNoApi, value));
+}
+
+INSTANTIATE_TEST_CASE_P(
+        HwApiTests, SetStringTest,
+        ValuesIn({
+                SetStringTest::MakeParam("calibration/f0_stored", &Vibrator::HwApi::setF0),
+                SetStringTest::MakeParam("calibration/redc_stored", &Vibrator::HwApi::setRedc),
+                SetStringTest::MakeParam("calibration/q_stored", &Vibrator::HwApi::setQ),
+        }),
+        SetStringTest::PrintParam);
+
+}  // namespace vibrator
+}  // namespace hardware
+}  // namespace android
+}  // namespace aidl

+ 386 - 0
vibrator/cs40l26/tests/test-hwcal.cpp

@@ -0,0 +1,386 @@
+/*
+ * Copyright (C) 2022 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 <android-base/file.h>
+#include <gtest/gtest.h>
+
+#include <fstream>
+
+#include "Hardware.h"
+
+namespace aidl {
+namespace android {
+namespace hardware {
+namespace vibrator {
+
+using ::testing::Test;
+
+class HwCalTest : public Test {
+  protected:
+    static constexpr std::array<uint32_t, 2> V_TICK_DEFAULT = {1, 100};
+    static constexpr std::array<uint32_t, 2> V_CLICK_DEFAULT = {1, 100};
+    static constexpr std::array<uint32_t, 2> V_LONG_DEFAULT = {1, 100};
+
+  public:
+    void SetUp() override { setenv("CALIBRATION_FILEPATH", mCalFile.path, true); }
+
+  private:
+    template <typename T>
+    static void pack(std::ostream &stream, const T &value, std::string lpad, std::string rpad) {
+        stream << lpad << value << rpad;
+    }
+
+    template <typename T, typename std::array<T, 0>::size_type N>
+    static void pack(std::ostream &stream, const std::array<T, N> &value, std::string lpad,
+                     std::string rpad) {
+        for (auto &entry : value) {
+            pack(stream, entry, lpad, rpad);
+        }
+    }
+
+  protected:
+    void createHwCal() { mHwCal = std::make_unique<HwCal>(); }
+
+    template <typename T>
+    void write(const std::string key, const T &value, std::string lpad = " ",
+               std::string rpad = "") {
+        std::ofstream calfile{mCalFile.path, std::ios_base::app};
+        calfile << key << ":";
+        pack(calfile, value, lpad, rpad);
+        calfile << std::endl;
+    }
+
+    void unlink() { ::unlink(mCalFile.path); }
+
+  protected:
+    std::unique_ptr<Vibrator::HwCal> mHwCal;
+    TemporaryFile mCalFile;
+};
+
+TEST_F(HwCalTest, f0_measured) {
+    uint32_t randInput = std::rand();
+    std::string expect = std::to_string(randInput);
+    std::string actual = std::to_string(~randInput);
+
+    write("f0_measured", expect);
+
+    createHwCal();
+
+    EXPECT_TRUE(mHwCal->getF0(&actual));
+    EXPECT_EQ(expect, actual);
+}
+
+TEST_F(HwCalTest, f0_missing) {
+    std::string actual;
+
+    createHwCal();
+
+    EXPECT_FALSE(mHwCal->getF0(&actual));
+}
+
+TEST_F(HwCalTest, redc_measured) {
+    uint32_t randInput = std::rand();
+    std::string expect = std::to_string(randInput);
+    std::string actual = std::to_string(~randInput);
+
+    write("redc_measured", expect);
+
+    createHwCal();
+
+    EXPECT_TRUE(mHwCal->getRedc(&actual));
+    EXPECT_EQ(expect, actual);
+}
+
+TEST_F(HwCalTest, redc_missing) {
+    std::string actual;
+
+    createHwCal();
+
+    EXPECT_FALSE(mHwCal->getRedc(&actual));
+}
+
+TEST_F(HwCalTest, q_measured) {
+    uint32_t randInput = std::rand();
+    std::string expect = std::to_string(randInput);
+    std::string actual = std::to_string(~randInput);
+
+    write("q_measured", expect);
+
+    createHwCal();
+
+    EXPECT_TRUE(mHwCal->getQ(&actual));
+    EXPECT_EQ(expect, actual);
+}
+
+TEST_F(HwCalTest, q_missing) {
+    std::string actual;
+
+    createHwCal();
+
+    EXPECT_FALSE(mHwCal->getQ(&actual));
+}
+
+TEST_F(HwCalTest, v_levels) {
+    std::array<uint32_t, 2> expect;
+    std::array<uint32_t, 2> actual;
+
+    // voltage for tick effects
+    std::transform(expect.begin(), expect.end(), actual.begin(), [](uint32_t &e) {
+        e = std::rand();
+        return ~e;
+    });
+
+    write("v_tick", expect);
+
+    createHwCal();
+
+    EXPECT_TRUE(mHwCal->getTickVolLevels(&actual));
+    EXPECT_EQ(expect, actual);
+
+    // voltage for click effects
+    std::transform(expect.begin(), expect.end(), actual.begin(), [](uint32_t &e) {
+        e = std::rand();
+        return ~e;
+    });
+
+    write("v_click", expect);
+
+    createHwCal();
+
+    EXPECT_TRUE(mHwCal->getClickVolLevels(&actual));
+    EXPECT_EQ(expect, actual);
+
+    // voltage for long effects
+    std::transform(expect.begin(), expect.end(), actual.begin(), [](uint32_t &e) {
+        e = std::rand();
+        return ~e;
+    });
+
+    write("v_long", expect);
+
+    createHwCal();
+
+    EXPECT_TRUE(mHwCal->getLongVolLevels(&actual));
+    EXPECT_EQ(expect, actual);
+}
+
+TEST_F(HwCalTest, v_missing) {
+    std::array<uint32_t, 2> expect = V_TICK_DEFAULT;
+    std::array<uint32_t, 2> actual;
+
+    std::transform(expect.begin(), expect.end(), actual.begin(), [](uint32_t &e) { return ~e; });
+
+    createHwCal();
+
+    EXPECT_TRUE(mHwCal->getTickVolLevels(&actual));
+    EXPECT_EQ(expect, actual);
+
+    expect = V_CLICK_DEFAULT;
+
+    std::transform(expect.begin(), expect.end(), actual.begin(), [](uint32_t &e) { return ~e; });
+
+    createHwCal();
+
+    EXPECT_TRUE(mHwCal->getClickVolLevels(&actual));
+    EXPECT_EQ(expect, actual);
+
+    expect = V_LONG_DEFAULT;
+
+    std::transform(expect.begin(), expect.end(), actual.begin(), [](uint32_t &e) { return ~e; });
+
+    createHwCal();
+
+    EXPECT_TRUE(mHwCal->getLongVolLevels(&actual));
+    EXPECT_EQ(expect, actual);
+}
+
+TEST_F(HwCalTest, v_short) {
+    std::array<uint32_t, 2> expect = V_TICK_DEFAULT;
+    std::array<uint32_t, 2> actual;
+
+    std::transform(expect.begin(), expect.end(), actual.begin(), [](uint32_t &e) { return ~e; });
+
+    write("v_tick", std::array<uint32_t, expect.size() - 1>());
+    write("v_click", std::array<uint32_t, expect.size() - 1>());
+    write("v_long", std::array<uint32_t, expect.size() - 1>());
+
+    createHwCal();
+
+    EXPECT_TRUE(mHwCal->getTickVolLevels(&actual));
+    EXPECT_EQ(expect, actual);
+
+    expect = V_CLICK_DEFAULT;
+    EXPECT_TRUE(mHwCal->getClickVolLevels(&actual));
+    EXPECT_EQ(expect, actual);
+
+    expect = V_LONG_DEFAULT;
+    EXPECT_TRUE(mHwCal->getLongVolLevels(&actual));
+    EXPECT_EQ(expect, actual);
+}
+
+TEST_F(HwCalTest, v_long) {
+    std::array<uint32_t, 2> expect = V_TICK_DEFAULT;
+    std::array<uint32_t, 2> actual;
+
+    std::transform(expect.begin(), expect.end(), actual.begin(), [](uint32_t &e) { return ~e; });
+
+    write("v_tick", std::array<uint32_t, expect.size() + 1>());
+    write("v_click", std::array<uint32_t, expect.size() + 1>());
+    write("v_long", std::array<uint32_t, expect.size() + 1>());
+
+    createHwCal();
+
+    EXPECT_TRUE(mHwCal->getTickVolLevels(&actual));
+    EXPECT_EQ(expect, actual);
+
+    expect = V_CLICK_DEFAULT;
+    EXPECT_TRUE(mHwCal->getClickVolLevels(&actual));
+    EXPECT_EQ(expect, actual);
+
+    expect = V_LONG_DEFAULT;
+    EXPECT_TRUE(mHwCal->getLongVolLevels(&actual));
+    EXPECT_EQ(expect, actual);
+}
+
+TEST_F(HwCalTest, v_nofile) {
+    std::array<uint32_t, 2> expect = V_TICK_DEFAULT;
+    std::array<uint32_t, 2> actual;
+
+    std::transform(expect.begin(), expect.end(), actual.begin(), [](uint32_t &e) { return ~e; });
+
+    write("v_tick", actual);
+    write("v_click", actual);
+    write("v_long", actual);
+    unlink();
+
+    createHwCal();
+
+    EXPECT_TRUE(mHwCal->getTickVolLevels(&actual));
+    EXPECT_EQ(expect, actual);
+
+    expect = V_CLICK_DEFAULT;
+    EXPECT_TRUE(mHwCal->getClickVolLevels(&actual));
+    EXPECT_EQ(expect, actual);
+
+    expect = V_LONG_DEFAULT;
+    EXPECT_TRUE(mHwCal->getLongVolLevels(&actual));
+    EXPECT_EQ(expect, actual);
+}
+
+TEST_F(HwCalTest, multiple) {
+    uint32_t randInput = std::rand();
+    std::string f0Expect = std::to_string(randInput);
+    std::string f0Actual = std::to_string(~randInput);
+    randInput = std::rand();
+    std::string redcExpect = std::to_string(randInput);
+    std::string redcActual = std::to_string(~randInput);
+    randInput = std::rand();
+    std::string qExpect = std::to_string(randInput);
+    std::string qActual = std::to_string(~randInput);
+    std::array<uint32_t, 2> volTickExpect, volClickExpect, volLongExpect;
+    std::array<uint32_t, 2> volActual;
+
+    std::transform(volTickExpect.begin(), volTickExpect.end(), volActual.begin(), [](uint32_t &e) {
+        e = std::rand();
+        return ~e;
+    });
+
+    write("f0_measured", f0Expect);
+    write("redc_measured", redcExpect);
+    write("q_measured", qExpect);
+    write("v_tick", volTickExpect);
+    std::transform(volClickExpect.begin(), volClickExpect.end(), volActual.begin(),
+                   [](uint32_t &e) {
+                       e = std::rand();
+                       return ~e;
+                   });
+    write("v_click", volClickExpect);
+    std::transform(volLongExpect.begin(), volLongExpect.end(), volActual.begin(), [](uint32_t &e) {
+        e = std::rand();
+        return ~e;
+    });
+    write("v_long", volLongExpect);
+
+    createHwCal();
+
+    EXPECT_TRUE(mHwCal->getF0(&f0Actual));
+    EXPECT_EQ(f0Expect, f0Actual);
+    EXPECT_TRUE(mHwCal->getRedc(&redcActual));
+    EXPECT_EQ(redcExpect, redcActual);
+    EXPECT_TRUE(mHwCal->getQ(&qActual));
+    EXPECT_EQ(qExpect, qActual);
+    EXPECT_TRUE(mHwCal->getTickVolLevels(&volActual));
+    EXPECT_EQ(volTickExpect, volActual);
+    EXPECT_TRUE(mHwCal->getClickVolLevels(&volActual));
+    EXPECT_EQ(volClickExpect, volActual);
+    EXPECT_TRUE(mHwCal->getLongVolLevels(&volActual));
+    EXPECT_EQ(volLongExpect, volActual);
+}
+
+TEST_F(HwCalTest, trimming) {
+    uint32_t randInput = std::rand();
+    std::string f0Expect = std::to_string(randInput);
+    std::string f0Actual = std::to_string(~randInput);
+    randInput = std::rand();
+    std::string redcExpect = std::to_string(randInput);
+    std::string redcActual = std::to_string(randInput);
+    randInput = std::rand();
+    std::string qExpect = std::to_string(randInput);
+    std::string qActual = std::to_string(randInput);
+    std::array<uint32_t, 2> volTickExpect, volClickExpect, volLongExpect;
+    std::array<uint32_t, 2> volActual;
+
+    std::transform(volTickExpect.begin(), volTickExpect.end(), volActual.begin(), [](uint32_t &e) {
+        e = std::rand();
+        return ~e;
+    });
+
+    write("f0_measured", f0Expect, " \t", "\t ");
+    write("redc_measured", redcExpect, " \t", "\t ");
+    write("q_measured", qExpect, " \t", "\t ");
+    write("v_tick", volTickExpect, " \t", "\t ");
+    std::transform(volClickExpect.begin(), volClickExpect.end(), volActual.begin(),
+                   [](uint32_t &e) {
+                       e = std::rand();
+                       return ~e;
+                   });
+    write("v_click", volClickExpect, " \t", "\t ");
+    std::transform(volLongExpect.begin(), volLongExpect.end(), volActual.begin(), [](uint32_t &e) {
+        e = std::rand();
+        return ~e;
+    });
+    write("v_long", volLongExpect, " \t", "\t ");
+
+    createHwCal();
+
+    EXPECT_TRUE(mHwCal->getF0(&f0Actual));
+    EXPECT_EQ(f0Expect, f0Actual);
+    EXPECT_TRUE(mHwCal->getRedc(&redcActual));
+    EXPECT_EQ(redcExpect, redcActual);
+    EXPECT_TRUE(mHwCal->getQ(&qActual));
+    EXPECT_EQ(qExpect, qActual);
+    EXPECT_TRUE(mHwCal->getTickVolLevels(&volActual));
+    EXPECT_EQ(volTickExpect, volActual);
+    EXPECT_TRUE(mHwCal->getClickVolLevels(&volActual));
+    EXPECT_EQ(volClickExpect, volActual);
+    EXPECT_TRUE(mHwCal->getLongVolLevels(&volActual));
+    EXPECT_EQ(volLongExpect, volActual);
+}
+
+}  // namespace vibrator
+}  // namespace hardware
+}  // namespace android
+}  // namespace aidl

+ 682 - 0
vibrator/cs40l26/tests/test-vibrator.cpp

@@ -0,0 +1,682 @@
+/*
+ * Copyright (C) 2022 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 <aidl/android/hardware/vibrator/BnVibratorCallback.h>
+#include <android-base/logging.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+#include <linux/input.h>
+#include <linux/uinput.h>
+
+#include <future>
+
+#include "Vibrator.h"
+#include "mocks.h"
+#include "types.h"
+#include "utils.h"
+
+namespace aidl {
+namespace android {
+namespace hardware {
+namespace vibrator {
+
+using ::testing::_;
+using ::testing::AnyNumber;
+using ::testing::Assign;
+using ::testing::AtLeast;
+using ::testing::AtMost;
+using ::testing::Combine;
+using ::testing::DoAll;
+using ::testing::DoDefault;
+using ::testing::Exactly;
+using ::testing::Expectation;
+using ::testing::ExpectationSet;
+using ::testing::Ge;
+using ::testing::Mock;
+using ::testing::MockFunction;
+using ::testing::Range;
+using ::testing::Return;
+using ::testing::Sequence;
+using ::testing::SetArgPointee;
+using ::testing::SetArgReferee;
+using ::testing::Test;
+using ::testing::TestParamInfo;
+using ::testing::ValuesIn;
+using ::testing::WithParamInterface;
+
+// Forward Declarations
+
+static EffectQueue Queue(const QueueEffect &effect);
+static EffectQueue Queue(const QueueDelay &delay);
+template <typename T, typename U, typename... Args>
+static EffectQueue Queue(const T &first, const U &second, Args... rest);
+
+static EffectLevel Level(float intensity, float levelLow, float levelHigh);
+static EffectScale Scale(float intensity, float levelLow, float levelHigh);
+
+// Constants With Arbitrary Values
+
+static constexpr uint32_t CAL_VERSION = 2;
+static constexpr std::array<EffectLevel, 2> V_TICK_DEFAULT = {1, 100};
+static constexpr std::array<EffectLevel, 2> V_CLICK_DEFAULT{1, 100};
+static constexpr std::array<EffectLevel, 2> V_LONG_DEFAULT{1, 100};
+static constexpr std::array<EffectDuration, 14> EFFECT_DURATIONS{
+        0, 100, 30, 1000, 300, 130, 150, 500, 100, 15, 20, 1000, 1000, 1000};
+
+// Constants With Prescribed Values
+
+static const std::map<Effect, EffectIndex> EFFECT_INDEX{
+        {Effect::CLICK, 2},
+        {Effect::TICK, 2},
+        {Effect::HEAVY_CLICK, 2},
+        {Effect::TEXTURE_TICK, 9},
+};
+static constexpr uint32_t MIN_ON_OFF_INTERVAL_US = 8500;
+static constexpr uint8_t VOLTAGE_SCALE_MAX = 100;
+static constexpr int8_t MAX_COLD_START_LATENCY_MS = 6;  // I2C Transaction + DSP Return-From-Standby
+static constexpr auto POLLING_TIMEOUT = 20;
+enum WaveformIndex : uint16_t {
+    /* Physical waveform */
+    WAVEFORM_LONG_VIBRATION_EFFECT_INDEX = 0,
+    WAVEFORM_RESERVED_INDEX_1 = 1,
+    WAVEFORM_CLICK_INDEX = 2,
+    WAVEFORM_SHORT_VIBRATION_EFFECT_INDEX = 3,
+    WAVEFORM_THUD_INDEX = 4,
+    WAVEFORM_SPIN_INDEX = 5,
+    WAVEFORM_QUICK_RISE_INDEX = 6,
+    WAVEFORM_SLOW_RISE_INDEX = 7,
+    WAVEFORM_QUICK_FALL_INDEX = 8,
+    WAVEFORM_LIGHT_TICK_INDEX = 9,
+    WAVEFORM_LOW_TICK_INDEX = 10,
+    WAVEFORM_RESERVED_MFG_1,
+    WAVEFORM_RESERVED_MFG_2,
+    WAVEFORM_RESERVED_MFG_3,
+    WAVEFORM_MAX_PHYSICAL_INDEX,
+    /* OWT waveform */
+    WAVEFORM_COMPOSE = WAVEFORM_MAX_PHYSICAL_INDEX,
+    WAVEFORM_PWLE,
+    /*
+     * Refer to <linux/input.h>, the WAVEFORM_MAX_INDEX must not exceed 96.
+     * #define FF_GAIN          0x60  // 96 in decimal
+     * #define FF_MAX_EFFECTS   FF_GAIN
+     */
+    WAVEFORM_MAX_INDEX,
+};
+
+static const EffectScale ON_GLOBAL_SCALE{levelToScale(V_LONG_DEFAULT[1])};
+static const EffectIndex ON_EFFECT_INDEX{0};
+
+static const std::map<EffectTuple, EffectScale> EFFECT_SCALE{
+        {{Effect::TICK, EffectStrength::LIGHT},
+         Scale(0.5f * 0.5f, V_CLICK_DEFAULT[0], V_CLICK_DEFAULT[1])},
+        {{Effect::TICK, EffectStrength::MEDIUM},
+         Scale(0.5f * 0.7f, V_CLICK_DEFAULT[0], V_CLICK_DEFAULT[1])},
+        {{Effect::TICK, EffectStrength::STRONG},
+         Scale(0.5f * 1.0f, V_CLICK_DEFAULT[0], V_CLICK_DEFAULT[1])},
+        {{Effect::CLICK, EffectStrength::LIGHT},
+         Scale(0.7f * 0.5f, V_CLICK_DEFAULT[0], V_CLICK_DEFAULT[1])},
+        {{Effect::CLICK, EffectStrength::MEDIUM},
+         Scale(0.7f * 0.7f, V_CLICK_DEFAULT[0], V_CLICK_DEFAULT[1])},
+        {{Effect::CLICK, EffectStrength::STRONG},
+         Scale(0.7f * 1.0f, V_CLICK_DEFAULT[0], V_CLICK_DEFAULT[1])},
+        {{Effect::HEAVY_CLICK, EffectStrength::LIGHT},
+         Scale(1.0f * 0.5f, V_CLICK_DEFAULT[0], V_CLICK_DEFAULT[1])},
+        {{Effect::HEAVY_CLICK, EffectStrength::MEDIUM},
+         Scale(1.0f * 0.7f, V_CLICK_DEFAULT[0], V_CLICK_DEFAULT[1])},
+        {{Effect::HEAVY_CLICK, EffectStrength::STRONG},
+         Scale(1.0f * 1.0f, V_CLICK_DEFAULT[0], V_CLICK_DEFAULT[1])},
+        {{Effect::TEXTURE_TICK, EffectStrength::LIGHT},
+         Scale(0.5f * 0.5f, V_TICK_DEFAULT[0], V_TICK_DEFAULT[1])},
+        {{Effect::TEXTURE_TICK, EffectStrength::MEDIUM},
+         Scale(0.5f * 0.7f, V_TICK_DEFAULT[0], V_TICK_DEFAULT[1])},
+        {{Effect::TEXTURE_TICK, EffectStrength::STRONG},
+         Scale(0.5f * 1.0f, V_TICK_DEFAULT[0], V_TICK_DEFAULT[1])},
+};
+
+static const std::map<EffectTuple, EffectQueue> EFFECT_QUEUE{
+        {{Effect::DOUBLE_CLICK, EffectStrength::LIGHT},
+         Queue(QueueEffect{EFFECT_INDEX.at(Effect::CLICK),
+                           Level(0.7f * 0.5f, V_CLICK_DEFAULT[0], V_CLICK_DEFAULT[1])},
+               100,
+               QueueEffect{EFFECT_INDEX.at(Effect::CLICK),
+                           Level(1.0f * 0.5f, V_CLICK_DEFAULT[0], V_CLICK_DEFAULT[1])})},
+        {{Effect::DOUBLE_CLICK, EffectStrength::MEDIUM},
+         Queue(QueueEffect{EFFECT_INDEX.at(Effect::CLICK),
+                           Level(0.7f * 0.7f, V_CLICK_DEFAULT[0], V_CLICK_DEFAULT[1])},
+               100,
+               QueueEffect{EFFECT_INDEX.at(Effect::CLICK),
+                           Level(1.0f * 0.7f, V_CLICK_DEFAULT[0], V_CLICK_DEFAULT[1])})},
+        {{Effect::DOUBLE_CLICK, EffectStrength::STRONG},
+         Queue(QueueEffect{EFFECT_INDEX.at(Effect::CLICK),
+                           Level(0.7f * 1.0f, V_CLICK_DEFAULT[0], V_CLICK_DEFAULT[1])},
+               100,
+               QueueEffect{EFFECT_INDEX.at(Effect::CLICK),
+                           Level(1.0f * 1.0f, V_CLICK_DEFAULT[0], V_CLICK_DEFAULT[1])})},
+};
+
+EffectQueue Queue(const QueueEffect &effect) {
+    auto index = std::get<0>(effect);
+    auto level = std::get<1>(effect);
+    auto string = std::to_string(index) + "." + std::to_string(level);
+    auto duration = EFFECT_DURATIONS[index];
+    return {string, duration};
+}
+
+EffectQueue Queue(const QueueDelay &delay) {
+    auto string = std::to_string(delay);
+    return {string, delay};
+}
+
+template <typename T, typename U, typename... Args>
+EffectQueue Queue(const T &first, const U &second, Args... rest) {
+    auto head = Queue(first);
+    auto tail = Queue(second, rest...);
+    auto string = std::get<0>(head) + "," + std::get<0>(tail);
+    auto duration = std::get<1>(head) + std::get<1>(tail);
+    return {string, duration};
+}
+
+static EffectLevel Level(float intensity, float levelLow, float levelHigh) {
+    return std::lround(intensity * (levelHigh - levelLow)) + levelLow;
+}
+
+static EffectScale Scale(float intensity, float levelLow, float levelHigh) {
+    return levelToScale(Level(intensity, levelLow, levelHigh));
+}
+
+class VibratorTest : public Test {
+  public:
+    void SetUp() override {
+        setenv("INPUT_EVENT_NAME", "CS40L26TestSuite", true);
+        std::unique_ptr<MockApi> mockapi;
+        std::unique_ptr<MockCal> mockcal;
+
+        createMock(&mockapi, &mockcal);
+        createVibrator(std::move(mockapi), std::move(mockcal));
+    }
+
+    void TearDown() override { deleteVibrator(); }
+
+  protected:
+    void createMock(std::unique_ptr<MockApi> *mockapi, std::unique_ptr<MockCal> *mockcal) {
+        *mockapi = std::make_unique<MockApi>();
+        *mockcal = std::make_unique<MockCal>();
+
+        mMockApi = mockapi->get();
+        mMockCal = mockcal->get();
+
+        ON_CALL(*mMockApi, destructor()).WillByDefault(Assign(&mMockApi, nullptr));
+
+        ON_CALL(*mMockApi, setFFGain(_, _)).WillByDefault(Return(true));
+        ON_CALL(*mMockApi, setFFEffect(_, _, _)).WillByDefault(Return(true));
+        ON_CALL(*mMockApi, setFFPlay(_, _, _)).WillByDefault(Return(true));
+        ON_CALL(*mMockApi, pollVibeState(_, _)).WillByDefault(Return(true));
+        ON_CALL(*mMockApi, uploadOwtEffect(_, _, _, _, _, _)).WillByDefault(Return(true));
+        ON_CALL(*mMockApi, eraseOwtEffect(_, _, _)).WillByDefault(Return(true));
+
+        ON_CALL(*mMockApi, getOwtFreeSpace(_))
+                .WillByDefault(DoAll(SetArgPointee<0>(11504), Return(true)));
+
+        ON_CALL(*mMockCal, destructor()).WillByDefault(Assign(&mMockCal, nullptr));
+
+        ON_CALL(*mMockCal, getVersion(_))
+                .WillByDefault(DoAll(SetArgPointee<0>(CAL_VERSION), Return(true)));
+
+        ON_CALL(*mMockCal, getTickVolLevels(_))
+                .WillByDefault(DoAll(SetArgPointee<0>(V_TICK_DEFAULT), Return(true)));
+        ON_CALL(*mMockCal, getClickVolLevels(_))
+                .WillByDefault(DoAll(SetArgPointee<0>(V_CLICK_DEFAULT), Return(true)));
+        ON_CALL(*mMockCal, getLongVolLevels(_))
+                .WillByDefault(DoAll(SetArgPointee<0>(V_LONG_DEFAULT), Return(true)));
+
+        relaxMock(false);
+    }
+
+    void createVibrator(std::unique_ptr<MockApi> mockapi, std::unique_ptr<MockCal> mockcal,
+                        bool relaxed = true) {
+        if (relaxed) {
+            relaxMock(true);
+        }
+        mVibrator = ndk::SharedRefBase::make<Vibrator>(std::move(mockapi), std::move(mockcal));
+        if (relaxed) {
+            relaxMock(false);
+        }
+    }
+
+    void deleteVibrator(bool relaxed = true) {
+        if (relaxed) {
+            relaxMock(true);
+        }
+        mVibrator.reset();
+    }
+
+  private:
+    void relaxMock(bool relax) {
+        auto times = relax ? AnyNumber() : Exactly(0);
+
+        Mock::VerifyAndClearExpectations(mMockApi);
+        Mock::VerifyAndClearExpectations(mMockCal);
+
+        EXPECT_CALL(*mMockApi, destructor()).Times(times);
+        EXPECT_CALL(*mMockApi, setF0(_)).Times(times);
+        EXPECT_CALL(*mMockApi, setF0Offset(_)).Times(times);
+        EXPECT_CALL(*mMockApi, setRedc(_)).Times(times);
+        EXPECT_CALL(*mMockApi, setQ(_)).Times(times);
+        EXPECT_CALL(*mMockApi, hasOwtFreeSpace()).Times(times);
+        EXPECT_CALL(*mMockApi, getOwtFreeSpace(_)).Times(times);
+        EXPECT_CALL(*mMockApi, setF0CompEnable(_)).Times(times);
+        EXPECT_CALL(*mMockApi, setRedcCompEnable(_)).Times(times);
+        EXPECT_CALL(*mMockApi, pollVibeState(_, _)).Times(times);
+        EXPECT_CALL(*mMockApi, setFFGain(_, _)).Times(times);
+        EXPECT_CALL(*mMockApi, setFFEffect(_, _, _)).Times(times);
+        EXPECT_CALL(*mMockApi, setFFPlay(_, _, _)).Times(times);
+        EXPECT_CALL(*mMockApi, setMinOnOffInterval(_)).Times(times);
+        EXPECT_CALL(*mMockApi, getHapticAlsaDevice(_, _)).Times(times);
+        EXPECT_CALL(*mMockApi, setHapticPcmAmp(_, _, _, _)).Times(times);
+
+        EXPECT_CALL(*mMockApi, debug(_)).Times(times);
+
+        EXPECT_CALL(*mMockCal, destructor()).Times(times);
+        EXPECT_CALL(*mMockCal, getF0(_)).Times(times);
+        EXPECT_CALL(*mMockCal, getRedc(_)).Times(times);
+        EXPECT_CALL(*mMockCal, getQ(_)).Times(times);
+        EXPECT_CALL(*mMockCal, getTickVolLevels(_)).Times(times);
+        EXPECT_CALL(*mMockCal, getClickVolLevels(_)).Times(times);
+        EXPECT_CALL(*mMockCal, getLongVolLevels(_)).Times(times);
+        EXPECT_CALL(*mMockCal, isChirpEnabled()).Times(times);
+        EXPECT_CALL(*mMockCal, getLongFrequencyShift(_)).Times(times);
+        EXPECT_CALL(*mMockCal, isF0CompEnabled()).Times(times);
+        EXPECT_CALL(*mMockCal, isRedcCompEnabled()).Times(times);
+        EXPECT_CALL(*mMockCal, debug(_)).Times(times);
+    }
+
+  protected:
+    MockApi *mMockApi;
+    MockCal *mMockCal;
+    std::shared_ptr<IVibrator> mVibrator;
+    uint32_t mEffectIndex;
+};
+
+TEST_F(VibratorTest, Constructor) {
+    std::unique_ptr<MockApi> mockapi;
+    std::unique_ptr<MockCal> mockcal;
+    std::string f0Val = std::to_string(std::rand());
+    std::string redcVal = std::to_string(std::rand());
+    std::string qVal = std::to_string(std::rand());
+    uint32_t calVer;
+    uint32_t supportedPrimitivesBits = 0x0;
+    Expectation volGet;
+    Sequence f0Seq, redcSeq, qSeq, supportedPrimitivesSeq;
+
+    EXPECT_CALL(*mMockApi, destructor()).WillOnce(DoDefault());
+    EXPECT_CALL(*mMockCal, destructor()).WillOnce(DoDefault());
+
+    deleteVibrator(false);
+
+    createMock(&mockapi, &mockcal);
+
+    EXPECT_CALL(*mMockCal, getF0(_))
+            .InSequence(f0Seq)
+            .WillOnce(DoAll(SetArgReferee<0>(f0Val), Return(true)));
+    EXPECT_CALL(*mMockApi, setF0(f0Val)).InSequence(f0Seq).WillOnce(Return(true));
+
+    EXPECT_CALL(*mMockCal, getRedc(_))
+            .InSequence(redcSeq)
+            .WillOnce(DoAll(SetArgReferee<0>(redcVal), Return(true)));
+    EXPECT_CALL(*mMockApi, setRedc(redcVal)).InSequence(redcSeq).WillOnce(Return(true));
+
+    EXPECT_CALL(*mMockCal, getQ(_))
+            .InSequence(qSeq)
+            .WillOnce(DoAll(SetArgReferee<0>(qVal), Return(true)));
+    EXPECT_CALL(*mMockApi, setQ(qVal)).InSequence(qSeq).WillOnce(Return(true));
+
+    EXPECT_CALL(*mMockCal, getLongFrequencyShift(_)).WillOnce(Return(true));
+
+    mMockCal->getVersion(&calVer);
+    if (calVer == 2) {
+        volGet = EXPECT_CALL(*mMockCal, getTickVolLevels(_)).WillOnce(DoDefault());
+        volGet = EXPECT_CALL(*mMockCal, getClickVolLevels(_)).WillOnce(DoDefault());
+        volGet = EXPECT_CALL(*mMockCal, getLongVolLevels(_)).WillOnce(DoDefault());
+    }
+
+    EXPECT_CALL(*mMockCal, isF0CompEnabled()).WillOnce(Return(true));
+    EXPECT_CALL(*mMockApi, setF0CompEnable(true)).WillOnce(Return(true));
+    EXPECT_CALL(*mMockCal, isRedcCompEnabled()).WillOnce(Return(true));
+    EXPECT_CALL(*mMockApi, setRedcCompEnable(true)).WillOnce(Return(true));
+
+    EXPECT_CALL(*mMockCal, isChirpEnabled()).WillOnce(Return(true));
+    EXPECT_CALL(*mMockCal, getSupportedPrimitives(_))
+            .InSequence(supportedPrimitivesSeq)
+            .WillOnce(DoAll(SetArgPointee<0>(supportedPrimitivesBits), Return(true)));
+
+    EXPECT_CALL(*mMockApi, setMinOnOffInterval(MIN_ON_OFF_INTERVAL_US)).WillOnce(Return(true));
+    createVibrator(std::move(mockapi), std::move(mockcal), false);
+}
+
+TEST_F(VibratorTest, on) {
+    Sequence s1, s2;
+    uint16_t duration = std::rand() + 1;
+
+    EXPECT_CALL(*mMockApi, setFFGain(_, ON_GLOBAL_SCALE)).InSequence(s1).WillOnce(DoDefault());
+    EXPECT_CALL(*mMockApi, setFFEffect(_, _, duration + MAX_COLD_START_LATENCY_MS))
+            .InSequence(s2)
+            .WillOnce(DoDefault());
+    EXPECT_CALL(*mMockApi, setFFPlay(_, ON_EFFECT_INDEX, true))
+            .InSequence(s1, s2)
+            .WillOnce(DoDefault());
+    EXPECT_TRUE(mVibrator->on(duration, nullptr).isOk());
+}
+
+TEST_F(VibratorTest, off) {
+    Sequence s1;
+    EXPECT_CALL(*mMockApi, setFFGain(_, ON_GLOBAL_SCALE)).InSequence(s1).WillOnce(DoDefault());
+    EXPECT_TRUE(mVibrator->off().isOk());
+}
+
+TEST_F(VibratorTest, supportsAmplitudeControl_supported) {
+    int32_t capabilities;
+    EXPECT_CALL(*mMockApi, hasOwtFreeSpace()).WillOnce(Return(true));
+    EXPECT_CALL(*mMockApi, getHapticAlsaDevice(_, _)).WillOnce(Return(true));
+
+    EXPECT_TRUE(mVibrator->getCapabilities(&capabilities).isOk());
+    EXPECT_GT(capabilities & IVibrator::CAP_AMPLITUDE_CONTROL, 0);
+}
+
+TEST_F(VibratorTest, supportsExternalAmplitudeControl_unsupported) {
+    int32_t capabilities;
+    EXPECT_CALL(*mMockApi, hasOwtFreeSpace()).WillOnce(Return(true));
+    EXPECT_CALL(*mMockApi, getHapticAlsaDevice(_, _)).WillOnce(Return(true));
+
+    EXPECT_TRUE(mVibrator->getCapabilities(&capabilities).isOk());
+    EXPECT_EQ(capabilities & IVibrator::CAP_EXTERNAL_AMPLITUDE_CONTROL, 0);
+}
+
+TEST_F(VibratorTest, setAmplitude_supported) {
+    EffectAmplitude amplitude = static_cast<float>(std::rand()) / RAND_MAX ?: 1.0f;
+
+    EXPECT_CALL(*mMockApi, setFFGain(_, amplitudeToScale(amplitude))).WillOnce(Return(true));
+
+    EXPECT_TRUE(mVibrator->setAmplitude(amplitude).isOk());
+}
+
+TEST_F(VibratorTest, supportsExternalControl_supported) {
+    int32_t capabilities;
+    EXPECT_CALL(*mMockApi, hasOwtFreeSpace()).WillOnce(Return(true));
+    EXPECT_CALL(*mMockApi, getHapticAlsaDevice(_, _)).WillOnce(Return(true));
+
+    EXPECT_TRUE(mVibrator->getCapabilities(&capabilities).isOk());
+    EXPECT_GT(capabilities & IVibrator::CAP_EXTERNAL_CONTROL, 0);
+}
+
+TEST_F(VibratorTest, supportsExternalControl_unsupported) {
+    int32_t capabilities;
+    EXPECT_CALL(*mMockApi, hasOwtFreeSpace()).WillOnce(Return(true));
+    EXPECT_CALL(*mMockApi, getHapticAlsaDevice(_, _)).WillOnce(Return(false));
+
+    EXPECT_TRUE(mVibrator->getCapabilities(&capabilities).isOk());
+    EXPECT_EQ(capabilities & IVibrator::CAP_EXTERNAL_CONTROL, 0);
+}
+
+TEST_F(VibratorTest, setExternalControl_enable) {
+    Sequence s1, s2;
+    EXPECT_CALL(*mMockApi, setFFGain(_, ON_GLOBAL_SCALE)).InSequence(s1).WillOnce(DoDefault());
+    EXPECT_CALL(*mMockApi, getHapticAlsaDevice(_, _)).InSequence(s2).WillOnce(Return(true));
+    EXPECT_CALL(*mMockApi, setHapticPcmAmp(_, true, _, _))
+            .InSequence(s1, s2)
+            .WillOnce(Return(true));
+
+    EXPECT_TRUE(mVibrator->setExternalControl(true).isOk());
+}
+
+TEST_F(VibratorTest, setExternalControl_disable) {
+    Sequence s1, s2, s3, s4;
+
+    // The default mIsUnderExternalControl is false, so it needs to turn on the External Control
+    // to make mIsUnderExternalControl become true.
+    EXPECT_CALL(*mMockApi, setFFGain(_, ON_GLOBAL_SCALE))
+            .InSequence(s1)
+            .InSequence(s1)
+            .WillOnce(DoDefault());
+    EXPECT_CALL(*mMockApi, getHapticAlsaDevice(_, _)).InSequence(s2).WillOnce(Return(true));
+    EXPECT_CALL(*mMockApi, setHapticPcmAmp(_, true, _, _)).InSequence(s3).WillOnce(Return(true));
+
+    EXPECT_TRUE(mVibrator->setExternalControl(true).isOk());
+
+    EXPECT_CALL(*mMockApi, setFFGain(_, levelToScale(VOLTAGE_SCALE_MAX)))
+            .InSequence(s4)
+            .WillOnce(DoDefault());
+    EXPECT_CALL(*mMockApi, setHapticPcmAmp(_, false, _, _))
+            .InSequence(s1, s2, s3, s4)
+            .WillOnce(Return(true));
+
+    EXPECT_TRUE(mVibrator->setExternalControl(false).isOk());
+}
+
+class EffectsTest : public VibratorTest, public WithParamInterface<EffectTuple> {
+  public:
+    static auto PrintParam(const TestParamInfo<ParamType> &info) {
+        auto param = info.param;
+        auto effect = std::get<0>(param);
+        auto strength = std::get<1>(param);
+        return toString(effect) + "_" + toString(strength);
+    }
+};
+
+TEST_P(EffectsTest, perform) {
+    auto param = GetParam();
+    auto effect = std::get<0>(param);
+    auto strength = std::get<1>(param);
+    auto scale = EFFECT_SCALE.find(param);
+    auto queue = EFFECT_QUEUE.find(param);
+    EffectDuration duration;
+    auto callback = ndk::SharedRefBase::make<MockVibratorCallback>();
+    std::promise<void> promise;
+    std::future<void> future{promise.get_future()};
+    auto complete = [&promise] {
+        promise.set_value();
+        return ndk::ScopedAStatus::ok();
+    };
+    bool composeEffect;
+
+    ExpectationSet eSetup;
+    Expectation eActivate, ePollHaptics, ePollStop, eEraseDone;
+
+    if (scale != EFFECT_SCALE.end()) {
+        EffectIndex index = EFFECT_INDEX.at(effect);
+        duration = EFFECT_DURATIONS[index];
+
+        eSetup += EXPECT_CALL(*mMockApi, setFFGain(_, levelToScale(scale->second)))
+                          .WillOnce(DoDefault());
+        eActivate = EXPECT_CALL(*mMockApi, setFFPlay(_, index, true))
+                            .After(eSetup)
+                            .WillOnce(DoDefault());
+    } else if (queue != EFFECT_QUEUE.end()) {
+        duration = std::get<1>(queue->second);
+        eSetup += EXPECT_CALL(*mMockApi, setFFGain(_, ON_GLOBAL_SCALE))
+                          .After(eSetup)
+                          .WillOnce(DoDefault());
+        eSetup += EXPECT_CALL(*mMockApi, getOwtFreeSpace(_)).WillOnce(DoDefault());
+        eSetup += EXPECT_CALL(*mMockApi, uploadOwtEffect(_, _, _, _, _, _))
+                          .After(eSetup)
+                          .WillOnce(DoDefault());
+        eActivate = EXPECT_CALL(*mMockApi, setFFPlay(_, WAVEFORM_COMPOSE, true))
+                            .After(eSetup)
+                            .WillOnce(DoDefault());
+        composeEffect = true;
+    } else {
+        duration = 0;
+    }
+
+    if (duration) {
+        ePollHaptics = EXPECT_CALL(*mMockApi, pollVibeState(1, POLLING_TIMEOUT))
+                               .After(eActivate)
+                               .WillOnce(DoDefault());
+        ePollStop = EXPECT_CALL(*mMockApi, pollVibeState(0, -1))
+                            .After(ePollHaptics)
+                            .WillOnce(DoDefault());
+        if (composeEffect) {
+            eEraseDone = EXPECT_CALL(*mMockApi, eraseOwtEffect(_, _, _))
+                                 .After(ePollStop)
+                                 .WillOnce(DoDefault());
+            EXPECT_CALL(*callback, onComplete()).After(eEraseDone).WillOnce(complete);
+        } else {
+            EXPECT_CALL(*callback, onComplete()).After(ePollStop).WillOnce(complete);
+        }
+    }
+
+    int32_t lengthMs;
+    ndk::ScopedAStatus status = mVibrator->perform(effect, strength, callback, &lengthMs);
+    if (status.isOk()) {
+        EXPECT_LE(duration, lengthMs);
+    } else {
+        EXPECT_EQ(EX_UNSUPPORTED_OPERATION, status.getExceptionCode());
+        EXPECT_EQ(0, lengthMs);
+    }
+
+    if (duration) {
+        EXPECT_EQ(future.wait_for(std::chrono::milliseconds(100)), std::future_status::ready);
+    }
+}
+
+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(),
+                                                   ndk::enum_range<EffectStrength>().end()};
+
+INSTANTIATE_TEST_CASE_P(VibratorTests, EffectsTest,
+                        Combine(ValuesIn(kEffects.begin(), kEffects.end()),
+                                ValuesIn(kEffectStrengths.begin(), kEffectStrengths.end())),
+                        EffectsTest::PrintParam);
+
+struct PrimitiveParam {
+    CompositePrimitive primitive;
+    EffectIndex index;
+};
+
+class PrimitiveTest : public VibratorTest, public WithParamInterface<PrimitiveParam> {
+  public:
+    static auto PrintParam(const TestParamInfo<ParamType> &info) {
+        return toString(info.param.primitive);
+    }
+};
+
+const std::vector<PrimitiveParam> kPrimitiveParams = {
+        {CompositePrimitive::CLICK, 2},      {CompositePrimitive::THUD, 4},
+        {CompositePrimitive::SPIN, 5},       {CompositePrimitive::QUICK_RISE, 6},
+        {CompositePrimitive::SLOW_RISE, 7},  {CompositePrimitive::QUICK_FALL, 8},
+        {CompositePrimitive::LIGHT_TICK, 9}, {CompositePrimitive::LOW_TICK, 10},
+};
+
+TEST_P(PrimitiveTest, getPrimitiveDuration) {
+    auto param = GetParam();
+    auto primitive = param.primitive;
+    auto index = param.index;
+    int32_t duration;
+
+    EXPECT_EQ(EX_NONE, mVibrator->getPrimitiveDuration(primitive, &duration).getExceptionCode());
+    EXPECT_EQ(EFFECT_DURATIONS[index], duration);
+}
+
+INSTANTIATE_TEST_CASE_P(VibratorTests, PrimitiveTest,
+                        ValuesIn(kPrimitiveParams.begin(), kPrimitiveParams.end()),
+                        PrimitiveTest::PrintParam);
+
+struct ComposeParam {
+    std::string name;
+    std::vector<CompositeEffect> composite;
+    EffectQueue queue;
+};
+
+class ComposeTest : public VibratorTest, public WithParamInterface<ComposeParam> {
+  public:
+    static auto PrintParam(const TestParamInfo<ParamType> &info) { return info.param.name; }
+};
+
+TEST_P(ComposeTest, compose) {
+    auto param = GetParam();
+    auto composite = param.composite;
+    auto queue = std::get<0>(param.queue);
+    ExpectationSet eSetup;
+    Expectation eActivate, ePollHaptics, ePollStop, eEraseDone;
+    auto callback = ndk::SharedRefBase::make<MockVibratorCallback>();
+    std::promise<void> promise;
+    std::future<void> future{promise.get_future()};
+    auto complete = [&promise] {
+        promise.set_value();
+        return ndk::ScopedAStatus::ok();
+    };
+
+    eSetup += EXPECT_CALL(*mMockApi, setFFGain(_, ON_GLOBAL_SCALE))
+                      .After(eSetup)
+                      .WillOnce(DoDefault());
+    eSetup += EXPECT_CALL(*mMockApi, getOwtFreeSpace(_)).WillOnce(DoDefault());
+    eSetup += EXPECT_CALL(*mMockApi, uploadOwtEffect(_, _, _, _, _, _))
+                      .After(eSetup)
+                      .WillOnce(DoDefault());
+    eActivate = EXPECT_CALL(*mMockApi, setFFPlay(_, WAVEFORM_COMPOSE, true))
+                        .After(eSetup)
+                        .WillOnce(DoDefault());
+
+    ePollHaptics = EXPECT_CALL(*mMockApi, pollVibeState(1, POLLING_TIMEOUT))
+                           .After(eActivate)
+                           .WillOnce(DoDefault());
+    ePollStop =
+            EXPECT_CALL(*mMockApi, pollVibeState(0, -1)).After(ePollHaptics).WillOnce(DoDefault());
+    eEraseDone =
+            EXPECT_CALL(*mMockApi, eraseOwtEffect(_, _, _)).After(ePollStop).WillOnce(DoDefault());
+    EXPECT_CALL(*callback, onComplete()).After(eEraseDone).WillOnce(complete);
+
+    EXPECT_EQ(EX_NONE, mVibrator->compose(composite, callback).getExceptionCode());
+
+    EXPECT_EQ(future.wait_for(std::chrono::milliseconds(100)), std::future_status::ready);
+}
+
+const std::vector<ComposeParam> kComposeParams = {
+        {"click",
+         {{0, CompositePrimitive::CLICK, 1.0f}},
+         Queue(QueueEffect(2, Level(1.0f, V_CLICK_DEFAULT[0], V_CLICK_DEFAULT[1])), 0)},
+        {"thud",
+         {{1, CompositePrimitive::THUD, 0.8f}},
+         Queue(1, QueueEffect(4, Level(0.8f, V_CLICK_DEFAULT[0], V_CLICK_DEFAULT[1])), 0)},
+        {"spin",
+         {{2, CompositePrimitive::SPIN, 0.6f}},
+         Queue(2, QueueEffect(5, Level(0.6f, V_CLICK_DEFAULT[0], V_CLICK_DEFAULT[1])), 0)},
+        {"quick_rise",
+         {{3, CompositePrimitive::QUICK_RISE, 0.4f}},
+         Queue(3, QueueEffect(6, Level(0.4f, V_LONG_DEFAULT[0], V_LONG_DEFAULT[1])), 0)},
+        {"slow_rise",
+         {{4, CompositePrimitive::SLOW_RISE, 0.0f}},
+         Queue(4, QueueEffect(7, Level(0.0f, V_CLICK_DEFAULT[0], V_CLICK_DEFAULT[1])), 0)},
+        {"quick_fall",
+         {{5, CompositePrimitive::QUICK_FALL, 1.0f}},
+         Queue(5, QueueEffect(8, Level(1.0f, V_LONG_DEFAULT[0], V_LONG_DEFAULT[1])), 0)},
+        {"pop",
+         {{6, CompositePrimitive::SLOW_RISE, 1.0f}, {50, CompositePrimitive::THUD, 1.0f}},
+         Queue(6, QueueEffect(7, Level(1.0f, V_CLICK_DEFAULT[0], V_CLICK_DEFAULT[1])), 50,
+               QueueEffect(4, Level(1.0f, V_CLICK_DEFAULT[0], V_CLICK_DEFAULT[1])), 0)},
+        {"snap",
+         {{7, CompositePrimitive::QUICK_RISE, 1.0f}, {0, CompositePrimitive::QUICK_FALL, 1.0f}},
+         Queue(7, QueueEffect(6, Level(1.0f, V_LONG_DEFAULT[0], V_LONG_DEFAULT[1])),
+               QueueEffect(8, Level(1.0f, V_LONG_DEFAULT[0], V_LONG_DEFAULT[1])), 0)},
+};
+
+INSTANTIATE_TEST_CASE_P(VibratorTests, ComposeTest,
+                        ValuesIn(kComposeParams.begin(), kComposeParams.end()),
+                        ComposeTest::PrintParam);
+}  // namespace vibrator
+}  // namespace hardware
+}  // namespace android
+}  // namespace aidl

+ 33 - 0
vibrator/cs40l26/tests/types.h

@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#ifndef ANDROID_HARDWARE_VIBRATOR_TEST_TYPES_H
+#define ANDROID_HARDWARE_VIBRATOR_TEST_TYPES_H
+
+#include <aidl/android/hardware/vibrator/IVibrator.h>
+
+using EffectIndex = uint16_t;
+using EffectLevel = uint32_t;
+using EffectAmplitude = float;
+using EffectScale = uint16_t;
+using EffectDuration = uint32_t;
+using EffectQueue = std::tuple<std::string, EffectDuration>;
+using EffectTuple = std::tuple<::aidl::android::hardware::vibrator::Effect,
+                               ::aidl::android::hardware::vibrator::EffectStrength>;
+
+using QueueEffect = std::tuple<EffectIndex, EffectLevel>;
+using QueueDelay = uint32_t;
+
+#endif  // ANDROID_HARDWARE_VIBRATOR_TEST_TYPES_H

+ 46 - 0
vibrator/cs40l26/tests/utils.h

@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#ifndef ANDROID_HARDWARE_VIBRATOR_TEST_UTILS_H
+#define ANDROID_HARDWARE_VIBRATOR_TEST_UTILS_H
+
+#include <cmath>
+
+#include "types.h"
+
+static inline EffectScale toScale(float amplitude, float maximum) {
+    float ratio = 100; /* Unit: % */
+    if (maximum != 0)
+        ratio = amplitude / maximum * 100;
+
+    if (maximum == 0 || ratio > 100)
+        ratio = 100;
+
+    return std::round(ratio);
+}
+
+static inline EffectScale levelToScale(EffectLevel level) {
+    return toScale(level, 100);
+}
+
+static inline EffectScale amplitudeToScale(EffectAmplitude amplitude) {
+    return toScale(amplitude, 1.0f);
+}
+
+static inline uint32_t msToCycles(EffectDuration ms) {
+    return ms * 48;
+}
+
+#endif  // ANDROID_HARDWARE_VIBRATOR_TEST_UTILS_H