From 5f1601be55f22119f7cce9fb2ccac7036a31ae1c Mon Sep 17 00:00:00 2001 From: Chase Wu Date: Thu, 28 Jul 2022 21:35:06 +0800 Subject: [PATCH 1/5] vibrator: Separate the audio coupled haptics setting Bug: 181615889 Test: trigger a2h via pixel ringtone and alarm apk Signed-off-by: Chase Wu Change-Id: Icd74aa8c46f939fdec68f0250cf18940f5bead33 --- audio/felix/config/mixer_paths.xml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/audio/felix/config/mixer_paths.xml b/audio/felix/config/mixer_paths.xml index d804dc1..0ca9108 100644 --- a/audio/felix/config/mixer_paths.xml +++ b/audio/felix/config/mixer_paths.xml @@ -44,9 +44,9 @@ - - - + + + From 89fda9efcb131c8ae3cc96b67575aa3b535b927f Mon Sep 17 00:00:00 2001 From: Chase Wu Date: Wed, 26 Oct 2022 22:15:13 +0800 Subject: [PATCH 2/5] cs40l26: Move the latest vibrator HAL to device This is the base version for the vibrator manager feature. Bug: 181615889 Test: Run all test suites Signed-off-by: Chase Wu Change-Id: I703835346864853a0d5a5918e3c50f541b67dc1f --- vibrator/Android.bp | 52 + vibrator/OWNERS | 3 + vibrator/common/Android.bp | 37 + vibrator/common/HardwareBase.cpp | 136 ++ vibrator/common/HardwareBase.h | 221 +++ vibrator/common/utils.h | 173 +++ vibrator/cs40l26/Android.bp | 86 ++ vibrator/cs40l26/Hardware.h | 346 +++++ vibrator/cs40l26/TEST_MAPPING | 10 + vibrator/cs40l26/Vibrator.cpp | 1327 +++++++++++++++++ vibrator/cs40l26/Vibrator.h | 211 +++ ...e.vibrator-service.cs40l26-dual-private.rc | 47 + ....vibrator-service.cs40l26-dual-private.xml | 7 + ...rdware.vibrator-service.cs40l26-private.rc | 47 + ...dware.vibrator-service.cs40l26-private.xml | 7 + vibrator/cs40l26/device-stereo.mk | 6 + vibrator/cs40l26/device.mk | 6 + vibrator/cs40l26/service.cpp | 55 + vibrator/cs40l26/tests/Android.bp | 35 + vibrator/cs40l26/tests/mocks.h | 80 + vibrator/cs40l26/tests/test-hwapi.cpp | 288 ++++ vibrator/cs40l26/tests/test-hwcal.cpp | 386 +++++ vibrator/cs40l26/tests/test-vibrator.cpp | 682 +++++++++ vibrator/cs40l26/tests/types.h | 33 + vibrator/cs40l26/tests/utils.h | 46 + 25 files changed, 4327 insertions(+) create mode 100644 vibrator/Android.bp create mode 100644 vibrator/OWNERS create mode 100644 vibrator/common/Android.bp create mode 100644 vibrator/common/HardwareBase.cpp create mode 100644 vibrator/common/HardwareBase.h create mode 100644 vibrator/common/utils.h create mode 100644 vibrator/cs40l26/Android.bp create mode 100644 vibrator/cs40l26/Hardware.h create mode 100644 vibrator/cs40l26/TEST_MAPPING create mode 100644 vibrator/cs40l26/Vibrator.cpp create mode 100644 vibrator/cs40l26/Vibrator.h create mode 100644 vibrator/cs40l26/android.hardware.vibrator-service.cs40l26-dual-private.rc create mode 100644 vibrator/cs40l26/android.hardware.vibrator-service.cs40l26-dual-private.xml create mode 100644 vibrator/cs40l26/android.hardware.vibrator-service.cs40l26-private.rc create mode 100644 vibrator/cs40l26/android.hardware.vibrator-service.cs40l26-private.xml create mode 100644 vibrator/cs40l26/device-stereo.mk create mode 100644 vibrator/cs40l26/device.mk create mode 100644 vibrator/cs40l26/service.cpp create mode 100644 vibrator/cs40l26/tests/Android.bp create mode 100644 vibrator/cs40l26/tests/mocks.h create mode 100644 vibrator/cs40l26/tests/test-hwapi.cpp create mode 100644 vibrator/cs40l26/tests/test-hwcal.cpp create mode 100644 vibrator/cs40l26/tests/test-vibrator.cpp create mode 100644 vibrator/cs40l26/tests/types.h create mode 100644 vibrator/cs40l26/tests/utils.h diff --git a/vibrator/Android.bp b/vibrator/Android.bp new file mode 100644 index 0000000..5d6d481 --- /dev/null +++ b/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: "PixelVibratorDefaultsPrivate", + relative_install_path: "hw", + static_libs: [ + "PixelVibratorCommonPrivate", + ], + shared_libs: [ + "libbase", + "libbinder_ndk", + "libcutils", + "libhardware", + "liblog", + "libutils", + ], +} + +cc_defaults { + name: "PixelVibratorBinaryDefaultsPrivate", + defaults: ["PixelVibratorDefaultsPrivate"], + shared_libs: [ + "android.hardware.vibrator-V2-ndk", + ], +} + +cc_defaults { + name: "PixelVibratorTestDefaultsPrivate", + defaults: ["PixelVibratorDefaultsPrivate"], + static_libs: [ + "android.hardware.vibrator-V2-ndk", + ], + test_suites: ["device-tests"], + require_root: true, +} diff --git a/vibrator/OWNERS b/vibrator/OWNERS new file mode 100644 index 0000000..5d4a9c3 --- /dev/null +++ b/vibrator/OWNERS @@ -0,0 +1,3 @@ +chasewu@google.com +michaelwr@google.com +taikuo@google.com diff --git a/vibrator/common/Android.bp b/vibrator/common/Android.bp new file mode 100644 index 0000000..c80f07c --- /dev/null +++ b/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: "PixelVibratorCommonPrivate", + srcs: [ + "HardwareBase.cpp", + ], + shared_libs: [ + "libbase", + "libcutils", + "liblog", + "libutils", + ], + cflags: [ + "-DATRACE_TAG=(ATRACE_TAG_VIBRATOR | ATRACE_TAG_HAL)", + "-DLOG_TAG=\"android.hardware.vibrator@1.x-common\"", + ], + export_include_dirs: ["."], + vendor_available: true, +} diff --git a/vibrator/common/HardwareBase.cpp b/vibrator/common/HardwareBase.cpp new file mode 100644 index 0000000..8e07e99 --- /dev/null +++ b/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 +#include + +#include +#include + +#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(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 diff --git a/vibrator/common/HardwareBase.h b/vibrator/common/HardwareBase.h new file mode 100644 index 0000000..448d29c --- /dev/null +++ b/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 +#include +#include +#include + +#include +#include +#include +#include + +#include "utils.h" + +namespace aidl { +namespace android { +namespace hardware { +namespace vibrator { + +using ::android::base::unique_fd; + +class HwApiBase { + private: + using NamesMap = std::map; + + class RecordInterface { + public: + virtual std::string toString(const NamesMap &names) = 0; + virtual ~RecordInterface() {} + }; + template + 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>; + + 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 + void open(const std::string &name, T *stream); + bool has(const std::ios &stream); + template + bool get(T *value, std::istream *stream); + template + bool set(const T &value, std::ostream *stream); + template + bool poll(const T &value, std::istream *stream, const int32_t timeout = -1); + template + 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 +void HwApiBase::open(const std::string &name, T *stream) { + saveName(name, stream); + utils::openNoCreate(mPathPrefix + name, stream); +} + +template +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 +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 +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 +void HwApiBase::record(const char *func, const T &value, const std::ios *stream) { + std::lock_guard lock(mRecordsMutex); + mRecords.emplace_back(std::make_unique>(func, value, stream)); + mRecords.pop_front(); +} + +template +std::string HwApiBase::Record::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 + bool getProperty(const char *key, T *value, const T defval); + template + bool getPersist(const char *key, T *value); + + private: + std::string mPropertyPrefix; + std::map mCalData; +}; + +template +bool HwCalBase::getProperty(const char *key, T *outval, const T defval) { + ATRACE_NAME("HwCal::getProperty"); + *outval = utils::getProperty(mPropertyPrefix + key, defval); + return true; +} + +template +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 diff --git a/vibrator/common/utils.h b/vibrator/common/utils.h new file mode 100644 index 0000000..188554d --- /dev/null +++ b/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 +#include +#include +#include + +#include +#include +#include + +namespace aidl { +namespace android { +namespace hardware { +namespace vibrator { +namespace utils { + +template +class Is_Iterable { + private: + template + static std::true_type test(typename U::iterator *u); + + template + static std::false_type test(U *u); + + public: + static const bool value = decltype(test(0))::value; +}; + +template +using Enable_If_Iterable = std::enable_if_t::value == B>; + +template +using Enable_If_Signed = std::enable_if_t, U>; + +template +using Enable_If_Unsigned = std::enable_if_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 +inline auto toUnderlying(const T value) { + return static_cast>(value); +} + +template +inline Enable_If_Iterable unpack(std::istream &stream, T *value) { + for (auto &entry : *value) { + stream >> entry; + } +} + +template +inline Enable_If_Iterable unpack(std::istream &stream, T *value) { + stream >> *value; +} + +template <> +inline void unpack(std::istream &stream, std::string *value) { + *value = std::string(std::istreambuf_iterator(stream), {}); + stream.setstate(std::istream::eofbit); +} + +template +inline Enable_If_Signed getProperty(const std::string &key, const T def) { + if (std::is_floating_point_v) { + 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 +inline Enable_If_Unsigned getProperty(const std::string &key, const T def) { + return ::android::base::GetUintProperty(key, def); +} + +template <> +inline bool getProperty(const std::string &key, const bool def) { + return ::android::base::GetBoolProperty(key, def); +} + +template +static void openNoCreate(const std::string &file, T *outStream) { + auto mode = std::is_base_of_v ? 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 +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 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 diff --git a/vibrator/cs40l26/Android.bp b/vibrator/cs40l26/Android.bp new file mode 100644 index 0000000..0d3b9f4 --- /dev/null +++ b/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-private", + cflags: [ + "-DATRACE_TAG=(ATRACE_TAG_VIBRATOR | ATRACE_TAG_HAL)", + "-DLOG_TAG=\"android.hardware.vibrator-cs40l26\"", + ], + shared_libs: [ + "libbinder", + ], +} + +cc_defaults { + name: "VibratorHalCs40l26BinaryDefaultsPrivate", + defaults: [ + "PixelVibratorBinaryDefaultsPrivate", + "android.hardware.vibrator-defaults.cs40l26-private", + ], + include_dirs: [ + "external/tinyalsa/include", + ], + shared_libs: [ + "libcutils", + "libtinyalsa", + ], +} + +cc_defaults { + name: "VibratorHalCs40l26TestDefaultsPrivate", + defaults: [ + "PixelVibratorTestDefaultsPrivate", + "android.hardware.vibrator-defaults.cs40l26-private", + ], + static_libs: [ + "android.hardware.vibrator-impl.cs40l26-private", + "libtinyalsa", + ], +} + +cc_library { + name: "android.hardware.vibrator-impl.cs40l26-private", + defaults: ["VibratorHalCs40l26BinaryDefaultsPrivate"], + srcs: ["Vibrator.cpp"], + export_include_dirs: ["."], + vendor_available: true, + visibility: [":__subpackages__"], +} + +cc_binary { + name: "android.hardware.vibrator-service.cs40l26-private", + defaults: ["VibratorHalCs40l26BinaryDefaultsPrivate"], + init_rc: ["android.hardware.vibrator-service.cs40l26-private.rc"], + vintf_fragments: ["android.hardware.vibrator-service.cs40l26-private.xml"], + srcs: ["service.cpp"], + shared_libs: ["android.hardware.vibrator-impl.cs40l26-private"], + proprietary: true, +} + +cc_binary { + name: "android.hardware.vibrator-service.cs40l26-dual-private", + defaults: ["VibratorHalCs40l26BinaryDefaultsPrivate"], + init_rc: ["android.hardware.vibrator-service.cs40l26-dual-private.rc"], + vintf_fragments: ["android.hardware.vibrator-service.cs40l26-dual-private.xml"], + srcs: ["service.cpp"], + shared_libs: ["android.hardware.vibrator-impl.cs40l26-private"], + cflags: ["-DVIBRATOR_NAME=\"dual\""], + proprietary: true, +} diff --git a/vibrator/cs40l26/Hardware.h b/vibrator/cs40l26/Hardware.h new file mode 100644 index 0000000..ae052ba --- /dev/null +++ b/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 + +#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 , 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(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 *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 V_TICK_DEFAULT = {1, 100}; + static constexpr std::array V_CLICK_DEFAULT = {1, 100}; + static constexpr std::array 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 *value) override { + if (getPersist(TICK_VOLTAGES_CONFIG, value)) { + return true; + } + *value = V_TICK_DEFAULT; + return true; + } + bool getClickVolLevels(std::array *value) override { + if (getPersist(CLICK_VOLTAGES_CONFIG, value)) { + return true; + } + *value = V_CLICK_DEFAULT; + return true; + } + bool getLongVolLevels(std::array *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 diff --git a/vibrator/cs40l26/TEST_MAPPING b/vibrator/cs40l26/TEST_MAPPING new file mode 100644 index 0000000..1d8ff7a --- /dev/null +++ b/vibrator/cs40l26/TEST_MAPPING @@ -0,0 +1,10 @@ +{ + "presubmit": [ + { + "name": "VibratorHalCs40l26TestSuite", + "keywords": [ + "nextgen" + ] + } + ] +} diff --git a/vibrator/cs40l26/Vibrator.cpp b/vibrator/cs40l26/Vibrator.cpp new file mode 100644 index 0000000..88914d8 --- /dev/null +++ b/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 +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#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 , 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 defaultSupportedPrimitives = { + ndk::enum_range().begin(), ndk::enum_range().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(data), + .current = reinterpret_cast(data), + .max = reinterpret_cast(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, std::unique_ptr 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(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(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 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 &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 &callback, + int32_t *_aidl_return) { + ATRACE_NAME("Vibrator::perform"); + return performEffect(effect, strength, callback, _aidl_return); +} + +ndk::ScopedAStatus Vibrator::getSupportedEffects(std::vector *_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 *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 &composite, + const std::shared_ptr &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 &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(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(timeoutMs); + if (!mHwApi->setFFEffect(mInputFd, &mFfEffects[effectIndex], + static_cast(timeoutMs))) { + ALOGE("Failed to edit effect %d (%d): %s", effectIndex, errno, strerror(errno)); + return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE); + } + } + + const std::scoped_lock 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 * /*_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(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(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 *_aidl_return) { + // TODO(b/170919640): complete implementation + int32_t capabilities; + Vibrator::getCapabilities(&capabilities); + if (capabilities & IVibrator::CAP_FREQUENCY_CONTROL) { + std::vector 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 *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, &, 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::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 &composite, + const std::shared_ptr &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 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(); + 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(); + 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(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 &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 &callback) { + setEffectAmplitude(volLevel, VOLTAGE_SCALE_MAX); + + return on(MAX_TIME_MS, effectIndex, ch, callback); +} + +void Vibrator::waitForComplete(std::shared_ptr &&callback) { + if (!mHwApi->pollVibeState(VIBE_STATE_HAPTIC, POLLING_TIMEOUT)) { + ALOGW("Failed to get state \"Haptic\""); + } + mHwApi->pollVibeState(VIBE_STATE_STOPPED); + + const std::scoped_lock 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 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 diff --git a/vibrator/cs40l26/Vibrator.h b/vibrator/cs40l26/Vibrator.h new file mode 100644 index 0000000..220c974 --- /dev/null +++ b/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 +#include +#include +#include + +#include +#include +#include + +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 *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 *value) = 0; + virtual bool getClickVolLevels(std::array *value) = 0; + virtual bool getLongVolLevels(std::array *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, std::unique_ptr hwcal); + + ndk::ScopedAStatus getCapabilities(int32_t *_aidl_return) override; + ndk::ScopedAStatus off() override; + ndk::ScopedAStatus on(int32_t timeoutMs, + const std::shared_ptr &callback) override; + ndk::ScopedAStatus perform(Effect effect, EffectStrength strength, + const std::shared_ptr &callback, + int32_t *_aidl_return) override; + ndk::ScopedAStatus getSupportedEffects(std::vector *_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 *supported) override; + ndk::ScopedAStatus getPrimitiveDuration(CompositePrimitive primitive, + int32_t *durationMs) override; + ndk::ScopedAStatus compose(const std::vector &composite, + const std::shared_ptr &callback) override; + ndk::ScopedAStatus getSupportedAlwaysOnEffects(std::vector *_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 *_aidl_return) override; + ndk::ScopedAStatus getPwlePrimitiveDurationMax(int32_t *durationMs) override; + ndk::ScopedAStatus getPwleCompositionSizeMax(int32_t *maxSize) override; + ndk::ScopedAStatus getSupportedBraking(std::vector *supported) override; + ndk::ScopedAStatus composePwle(const std::vector &composite, + const std::shared_ptr &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 &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 &callback, + int32_t *outTimeMs); + ndk::ScopedAStatus performEffect(uint32_t effectIndex, uint32_t volLevel, + struct dspmem_chunk *ch, + const std::shared_ptr &callback); + ndk::ScopedAStatus setPwle(const std::string &pwleQueue); + bool isUnderExternalControl(); + void waitForComplete(std::shared_ptr &&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 mHwApi; + std::unique_ptr mHwCal; + uint32_t mF0Offset; + std::array mTickEffectVol; + std::array mClickEffectVol; + std::array mLongEffectVol; + std::vector mFfEffects; + std::vector mEffectDurations; + std::future 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 mSupportedPrimitives; + bool mConfigHapticAlsaDeviceDone{false}; +}; + +} // namespace vibrator +} // namespace hardware +} // namespace android +} // namespace aidl diff --git a/vibrator/cs40l26/android.hardware.vibrator-service.cs40l26-dual-private.rc b/vibrator/cs40l26/android.hardware.vibrator-service.cs40l26-dual-private.rc new file mode 100644 index 0000000..07a43ff --- /dev/null +++ b/vibrator/cs40l26/android.hardware.vibrator-service.cs40l26-dual-private.rc @@ -0,0 +1,47 @@ +on property:vendor.all.modules.ready=1 + wait /sys/bus/i2c/devices/i2c-cs40l26a-dual/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_dual.cal + chown system system /mnt/vendor/persist/haptics + chown system system /mnt/vendor/persist/haptics/cs40l26_dual.cal + + chown system system /sys/bus/i2c/devices/i2c-cs40l26a-dual/calibration/f0_stored + chown system system /sys/bus/i2c/devices/i2c-cs40l26a-dual/calibration/q_stored + chown system system /sys/bus/i2c/devices/i2c-cs40l26a-dual/calibration/redc_stored + chown system system /sys/bus/i2c/devices/i2c-cs40l26a-dual/default/vibe_state + chown system system /sys/bus/i2c/devices/i2c-cs40l26a-dual/default/num_waves + chown system system /sys/bus/i2c/devices/i2c-cs40l26a-dual/default/f0_offset + chown system system /sys/bus/i2c/devices/i2c-cs40l26a-dual/default/owt_free_space + chown system system /sys/bus/i2c/devices/i2c-cs40l26a-dual/default/f0_comp_enable + chown system system /sys/bus/i2c/devices/i2c-cs40l26a-dual/default/redc_comp_enable + chown system system /sys/bus/i2c/devices/i2c-cs40l26a-dual/default/delay_before_stop_playback_us + + enable vendor.vibrator.cs40l26-dual + +service vendor.vibrator.cs40l26-dual /vendor/bin/hw/android.hardware.vibrator-service.cs40l26-dual-private + class hal + user system + group system input + + setenv INPUT_EVENT_NAME cs40l26_dual_input + setenv INPUT_EVENT_PATH /dev/input/event* + setenv PROPERTY_PREFIX ro.vendor.vibrator.hal. + setenv CALIBRATION_FILEPATH /mnt/vendor/persist/haptics/cs40l26_dual.cal + + setenv HWAPI_PATH_PREFIX /sys/bus/i2c/devices/i2c-cs40l26a-dual/ + 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 diff --git a/vibrator/cs40l26/android.hardware.vibrator-service.cs40l26-dual-private.xml b/vibrator/cs40l26/android.hardware.vibrator-service.cs40l26-dual-private.xml new file mode 100644 index 0000000..1bd3e7e --- /dev/null +++ b/vibrator/cs40l26/android.hardware.vibrator-service.cs40l26-dual-private.xml @@ -0,0 +1,7 @@ + + + android.hardware.vibrator + 2 + IVibrator/dual + + diff --git a/vibrator/cs40l26/android.hardware.vibrator-service.cs40l26-private.rc b/vibrator/cs40l26/android.hardware.vibrator-service.cs40l26-private.rc new file mode 100644 index 0000000..bbc0135 --- /dev/null +++ b/vibrator/cs40l26/android.hardware.vibrator-service.cs40l26-private.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-private + 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 diff --git a/vibrator/cs40l26/android.hardware.vibrator-service.cs40l26-private.xml b/vibrator/cs40l26/android.hardware.vibrator-service.cs40l26-private.xml new file mode 100644 index 0000000..4db8f8c --- /dev/null +++ b/vibrator/cs40l26/android.hardware.vibrator-service.cs40l26-private.xml @@ -0,0 +1,7 @@ + + + android.hardware.vibrator + 2 + IVibrator/default + + diff --git a/vibrator/cs40l26/device-stereo.mk b/vibrator/cs40l26/device-stereo.mk new file mode 100644 index 0000000..44d2b92 --- /dev/null +++ b/vibrator/cs40l26/device-stereo.mk @@ -0,0 +1,6 @@ +PRODUCT_PACKAGES += \ + android.hardware.vibrator-service.cs40l26 \ + android.hardware.vibrator-service.cs40l26-dual \ + +BOARD_SEPOLICY_DIRS += \ + hardware/google/pixel-sepolicy/vibrator/cs40l26 \ diff --git a/vibrator/cs40l26/device.mk b/vibrator/cs40l26/device.mk new file mode 100644 index 0000000..fe4bbc8 --- /dev/null +++ b/vibrator/cs40l26/device.mk @@ -0,0 +1,6 @@ +PRODUCT_PACKAGES += \ + android.hardware.vibrator-service.cs40l26-private + +BOARD_SEPOLICY_DIRS += \ + device/google/felix-sepolicy/vibrator/common \ + device/google/felix-sepolicy/vibrator/cs40l26 diff --git a/vibrator/cs40l26/service.cpp b/vibrator/cs40l26/service.cpp new file mode 100644 index 0000000..27173d9 --- /dev/null +++ b/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 +#include +#include +#include +#include + +#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(std::make_unique(), + std::make_unique()); + 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 +} diff --git a/vibrator/cs40l26/tests/Android.bp b/vibrator/cs40l26/tests/Android.bp new file mode 100644 index 0000000..6061ce8 --- /dev/null +++ b/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: "VibratorHalCs40l26TestSuitePrivate", + defaults: ["VibratorHalCs40l26TestDefaultsPrivate"], + srcs: [ + "test-hwcal.cpp", + "test-hwapi.cpp", + "test-vibrator.cpp", + ], + static_libs: [ + "libc++fs", + "libgmock", + ], + shared_libs: [ + "libbase", + ], +} diff --git a/vibrator/cs40l26/tests/mocks.h b/vibrator/cs40l26/tests/mocks.h new file mode 100644 index 0000000..21466a0 --- /dev/null +++ b/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 + +#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 *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 *value)); + MOCK_METHOD1(getClickVolLevels, bool(std::array *value)); + MOCK_METHOD1(getLongVolLevels, bool(std::array *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 diff --git a/vibrator/cs40l26/tests/test-hwapi.cpp b/vibrator/cs40l26/tests/test-hwapi.cpp new file mode 100644 index 0000000..cc4d465 --- /dev/null +++ b/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 +#include +#include + +#include +#include + +#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(); + + 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(); + } + + 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 + void expectContent(const std::string &name, const T &value) { + mExpectedContent[name] << value << std::endl; + } + + // Set actual file content for an input test. + template + void updateContent(const std::string &name, const T &value) { + std::ofstream(mFileMap[name]) << value << std::endl; + } + + template + 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(file), + std::istreambuf_iterator()); + EXPECT_EQ(expect, actual) << a.first; + } + } + + protected: + std::unique_ptr mHwApi; + std::unique_ptr mNoApi; + std::map mFileMap; + TemporaryDir mFilesDir; + TemporaryDir mEmptyDir; + std::map mExpectedContent; +}; + +template +class HwApiTypedTest : public HwApiTest, + public WithParamInterface>> { + public: + static auto PrintParam(const TestParamInfo &info) { + return ParamNameFixup(std::get<0>(info.param)); + } + static auto MakeParam(std::string name, std::function func) { + return std::make_tuple(name, func); + } +}; + +using HasTest = HwApiTypedTest; + +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; + +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; + +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; + +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; + +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 diff --git a/vibrator/cs40l26/tests/test-hwcal.cpp b/vibrator/cs40l26/tests/test-hwcal.cpp new file mode 100644 index 0000000..e482b6c --- /dev/null +++ b/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 +#include + +#include + +#include "Hardware.h" + +namespace aidl { +namespace android { +namespace hardware { +namespace vibrator { + +using ::testing::Test; + +class HwCalTest : public Test { + protected: + static constexpr std::array V_TICK_DEFAULT = {1, 100}; + static constexpr std::array V_CLICK_DEFAULT = {1, 100}; + static constexpr std::array V_LONG_DEFAULT = {1, 100}; + + public: + void SetUp() override { setenv("CALIBRATION_FILEPATH", mCalFile.path, true); } + + private: + template + static void pack(std::ostream &stream, const T &value, std::string lpad, std::string rpad) { + stream << lpad << value << rpad; + } + + template ::size_type N> + static void pack(std::ostream &stream, const std::array &value, std::string lpad, + std::string rpad) { + for (auto &entry : value) { + pack(stream, entry, lpad, rpad); + } + } + + protected: + void createHwCal() { mHwCal = std::make_unique(); } + + template + 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 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 expect; + std::array 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 expect = V_TICK_DEFAULT; + std::array 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 expect = V_TICK_DEFAULT; + std::array actual; + + std::transform(expect.begin(), expect.end(), actual.begin(), [](uint32_t &e) { return ~e; }); + + write("v_tick", std::array()); + write("v_click", std::array()); + write("v_long", std::array()); + + 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 expect = V_TICK_DEFAULT; + std::array actual; + + std::transform(expect.begin(), expect.end(), actual.begin(), [](uint32_t &e) { return ~e; }); + + write("v_tick", std::array()); + write("v_click", std::array()); + write("v_long", std::array()); + + 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 expect = V_TICK_DEFAULT; + std::array 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 volTickExpect, volClickExpect, volLongExpect; + std::array 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 volTickExpect, volClickExpect, volLongExpect; + std::array 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 diff --git a/vibrator/cs40l26/tests/test-vibrator.cpp b/vibrator/cs40l26/tests/test-vibrator.cpp new file mode 100644 index 0000000..a8bedd5 --- /dev/null +++ b/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 +#include +#include +#include +#include +#include + +#include + +#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 +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 V_TICK_DEFAULT = {1, 100}; +static constexpr std::array V_CLICK_DEFAULT{1, 100}; +static constexpr std::array V_LONG_DEFAULT{1, 100}; +static constexpr std::array 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_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 , 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 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 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 +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; + std::unique_ptr mockcal; + + createMock(&mockapi, &mockcal); + createVibrator(std::move(mockapi), std::move(mockcal)); + } + + void TearDown() override { deleteVibrator(); } + + protected: + void createMock(std::unique_ptr *mockapi, std::unique_ptr *mockcal) { + *mockapi = std::make_unique(); + *mockcal = std::make_unique(); + + 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, std::unique_ptr mockcal, + bool relaxed = true) { + if (relaxed) { + relaxMock(true); + } + mVibrator = ndk::SharedRefBase::make(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 mVibrator; + uint32_t mEffectIndex; +}; + +TEST_F(VibratorTest, Constructor) { + std::unique_ptr mockapi; + std::unique_ptr 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(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 { + public: + static auto PrintParam(const TestParamInfo &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(); + std::promise promise; + std::future 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 kEffects{ndk::enum_range().begin(), + ndk::enum_range().end()}; +const std::vector kEffectStrengths{ndk::enum_range().begin(), + ndk::enum_range().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 { + public: + static auto PrintParam(const TestParamInfo &info) { + return toString(info.param.primitive); + } +}; + +const std::vector 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 composite; + EffectQueue queue; +}; + +class ComposeTest : public VibratorTest, public WithParamInterface { + public: + static auto PrintParam(const TestParamInfo &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(); + std::promise promise; + std::future 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 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 diff --git a/vibrator/cs40l26/tests/types.h b/vibrator/cs40l26/tests/types.h new file mode 100644 index 0000000..e05c648 --- /dev/null +++ b/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 + +using EffectIndex = uint16_t; +using EffectLevel = uint32_t; +using EffectAmplitude = float; +using EffectScale = uint16_t; +using EffectDuration = uint32_t; +using EffectQueue = std::tuple; +using EffectTuple = std::tuple<::aidl::android::hardware::vibrator::Effect, + ::aidl::android::hardware::vibrator::EffectStrength>; + +using QueueEffect = std::tuple; +using QueueDelay = uint32_t; + +#endif // ANDROID_HARDWARE_VIBRATOR_TEST_TYPES_H diff --git a/vibrator/cs40l26/tests/utils.h b/vibrator/cs40l26/tests/utils.h new file mode 100644 index 0000000..e7f6055 --- /dev/null +++ b/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 + +#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 From fd5269155322b5c5e09a4d1e8ee6c64d2baf5f7b Mon Sep 17 00:00:00 2001 From: Chase Wu Date: Wed, 26 Oct 2022 22:15:47 +0800 Subject: [PATCH 3/5] [DO NOT MERGE] cs40l26: using the project folder mk The based mk file is different from master one, so there is no need to merge this patch to master branch. Bug: 181615889 Test: Run all test suites Signed-off-by: Chase Wu Change-Id: I61106aee64c87f77cef966d307ca5f047b5cc0b2 --- device-felix.mk | 5 +---- vibrator/cs40l26/device-stereo.mk | 3 ++- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/device-felix.mk b/device-felix.mk index dc15424..2db1ac9 100644 --- a/device-felix.mk +++ b/device-felix.mk @@ -30,7 +30,7 @@ DEVICE_PACKAGE_OVERLAYS += device/google/felix/felix/overlay include device/google/felix/audio/felix/audio-tables.mk include device/google/gs201/device-shipping-common.mk $(call soong_config_set,fp_hal_feature,pixel_product, product_a) -include hardware/google/pixel/vibrator/cs40l26/device-stereo.mk +include device/google/felix/vibrator/cs40l26/device-stereo.mk include device/google/gs101/bluetooth/bluetooth.mk ifeq ($(filter factory_felix, $(TARGET_PRODUCT)),) include device/google/felix/uwb/uwb_calibration.mk @@ -41,9 +41,6 @@ $(call soong_config_set,lyric,camera_hardware,felix) $(call soong_config_set,lyric,tuning_product,felix) $(call soong_config_set,google3a_config,target_device,felix) -BOARD_SEPOLICY_DIRS += \ - hardware/google/pixel-sepolicy/vibrator/common \ - # Init files PRODUCT_COPY_FILES += \ device/google/felix/conf/init.felix.rc:$(TARGET_COPY_OUT_VENDOR)/etc/init/hw/init.felix.rc diff --git a/vibrator/cs40l26/device-stereo.mk b/vibrator/cs40l26/device-stereo.mk index 44d2b92..08e4787 100644 --- a/vibrator/cs40l26/device-stereo.mk +++ b/vibrator/cs40l26/device-stereo.mk @@ -3,4 +3,5 @@ PRODUCT_PACKAGES += \ android.hardware.vibrator-service.cs40l26-dual \ BOARD_SEPOLICY_DIRS += \ - hardware/google/pixel-sepolicy/vibrator/cs40l26 \ + device/google/felix-sepolicy/vibrator/common \ + device/google/felix-sepolicy/vibrator/cs40l26 From 3584a679552b6002f4aef1673ff5f2ba234894d5 Mon Sep 17 00:00:00 2001 From: Chase Wu Date: Tue, 25 Oct 2022 22:14:22 +0800 Subject: [PATCH 4/5] cs40l26: Add vibrator manager support Bug: 181615889 Test: Manual type and trigger a long/short vibration Test: cmd vibrator_manager synced xxxx Test: cmd vibrator_manager sequential -v 0 xxxx Test: atest VtsHalVibratorManagerTargetTest \ VtsHalVibratorTargetTest android.os.cts.VibratorTest \ android.os.cts.VibratorManagerTest android.os.cts.VibrationEffectTest \ android.os.cts.VibrationAttributesTest \ android.os.cts.CombinedVibrationTest \ Signed-off-by: Chase Wu Change-Id: Ib93e8eb4a0de9269116e07f76b66a77b58915211 --- vibrator/cs40l26/Android.bp | 48 +++- vibrator/cs40l26/Hardware.h | 11 + vibrator/cs40l26/VibMgrHwApi.h | 123 ++++++++++ vibrator/cs40l26/Vibrator.cpp | 156 +++++++++++-- vibrator/cs40l26/Vibrator.h | 19 ++ vibrator/cs40l26/VibratorManager.cpp | 210 ++++++++++++++++++ vibrator/cs40l26/VibratorManager.h | 83 +++++++ vibrator/cs40l26/VibratorSync.cpp | 39 ++++ vibrator/cs40l26/VibratorSync.h | 46 ++++ vibrator/cs40l26/aidl/Android.bp | 13 ++ .../hardware/vibrator/IVibratorSync.aidl | 24 ++ .../vibrator/IVibratorSyncCallback.aidl | 21 ++ ...vibrator-service.cs40l26-stereo-private.rc | 14 ++ ...ibrator-service.cs40l26-stereo-private.xml | 7 + vibrator/cs40l26/device-stereo.mk | 5 +- vibrator/cs40l26/service-stereo.cpp | 82 +++++++ vibrator/cs40l26/service.cpp | 7 + vibrator/cs40l26/tests/mocks.h | 1 + 18 files changed, 876 insertions(+), 33 deletions(-) create mode 100644 vibrator/cs40l26/VibMgrHwApi.h create mode 100644 vibrator/cs40l26/VibratorManager.cpp create mode 100644 vibrator/cs40l26/VibratorManager.h create mode 100644 vibrator/cs40l26/VibratorSync.cpp create mode 100644 vibrator/cs40l26/VibratorSync.h create mode 100644 vibrator/cs40l26/aidl/Android.bp create mode 100644 vibrator/cs40l26/aidl/android/hardware/vibrator/IVibratorSync.aidl create mode 100644 vibrator/cs40l26/aidl/android/hardware/vibrator/IVibratorSyncCallback.aidl create mode 100644 vibrator/cs40l26/android.hardware.vibrator-service.cs40l26-stereo-private.rc create mode 100644 vibrator/cs40l26/android.hardware.vibrator-service.cs40l26-stereo-private.xml create mode 100644 vibrator/cs40l26/service-stereo.cpp diff --git a/vibrator/cs40l26/Android.bp b/vibrator/cs40l26/Android.bp index 0d3b9f4..7857580 100644 --- a/vibrator/cs40l26/Android.bp +++ b/vibrator/cs40l26/Android.bp @@ -21,7 +21,6 @@ cc_defaults { name: "android.hardware.vibrator-defaults.cs40l26-private", cflags: [ "-DATRACE_TAG=(ATRACE_TAG_VIBRATOR | ATRACE_TAG_HAL)", - "-DLOG_TAG=\"android.hardware.vibrator-cs40l26\"", ], shared_libs: [ "libbinder", @@ -34,13 +33,14 @@ cc_defaults { "PixelVibratorBinaryDefaultsPrivate", "android.hardware.vibrator-defaults.cs40l26-private", ], - include_dirs: [ - "external/tinyalsa/include", - ], shared_libs: [ + "android.hardware.vibrator.cs40l26-private-cpp", "libcutils", "libtinyalsa", ], + include_dirs: [ + "external/tinyalsa/include", + ], } cc_defaults { @@ -53,12 +53,19 @@ cc_defaults { "android.hardware.vibrator-impl.cs40l26-private", "libtinyalsa", ], + shared_libs: [ + "android.hardware.vibrator.cs40l26-private-cpp", + ], } cc_library { name: "android.hardware.vibrator-impl.cs40l26-private", defaults: ["VibratorHalCs40l26BinaryDefaultsPrivate"], - srcs: ["Vibrator.cpp"], + srcs: [ + "Vibrator.cpp", + "VibratorSync.cpp", + "VibratorManager.cpp", + ], export_include_dirs: ["."], vendor_available: true, visibility: [":__subpackages__"], @@ -70,7 +77,12 @@ cc_binary { init_rc: ["android.hardware.vibrator-service.cs40l26-private.rc"], vintf_fragments: ["android.hardware.vibrator-service.cs40l26-private.xml"], srcs: ["service.cpp"], - shared_libs: ["android.hardware.vibrator-impl.cs40l26-private"], + shared_libs: [ + "android.hardware.vibrator-impl.cs40l26-private", + ], + cflags: [ + "-DLOG_TAG=\"android.hardware.vibrator-cs40l26-private\"", + ], proprietary: true, } @@ -80,7 +92,27 @@ cc_binary { init_rc: ["android.hardware.vibrator-service.cs40l26-dual-private.rc"], vintf_fragments: ["android.hardware.vibrator-service.cs40l26-dual-private.xml"], srcs: ["service.cpp"], - shared_libs: ["android.hardware.vibrator-impl.cs40l26-private"], - cflags: ["-DVIBRATOR_NAME=\"dual\""], + shared_libs: [ + "android.hardware.vibrator-impl.cs40l26-private", + ], + cflags: [ + "-DVIBRATOR_NAME=\"dual\"", + "-DLOG_TAG=\"android.hardware.vibrator-cs40l26-dual-private\"", + ], + proprietary: true, +} + +cc_binary { + name: "android.hardware.vibrator-service.cs40l26-stereo-private", + defaults: ["VibratorHalCs40l26BinaryDefaultsPrivate"], + init_rc: ["android.hardware.vibrator-service.cs40l26-stereo-private.rc"], + vintf_fragments: ["android.hardware.vibrator-service.cs40l26-stereo-private.xml"], + srcs: ["service-stereo.cpp"], + shared_libs: [ + "android.hardware.vibrator-impl.cs40l26-private", + ], + cflags: [ + "-DLOG_TAG=\"android.hardware.vibrator-cs40l26-stereo-private\"", + ], proprietary: true, } diff --git a/vibrator/cs40l26/Hardware.h b/vibrator/cs40l26/Hardware.h index ae052ba..9cf831c 100644 --- a/vibrator/cs40l26/Hardware.h +++ b/vibrator/cs40l26/Hardware.h @@ -250,6 +250,17 @@ class HwApi : public Vibrator::HwApi, private HwApiBase { } return true; } + void clearTrigBtn(int fd, struct ff_effect *effect, int8_t id) override { + if ((*effect).trigger.button != 0x00) { + (*effect).trigger.button = 0x00; + if (id < WAVEFORM_MAX_PHYSICAL_INDEX) { + /* Clear the trigger pin setting */ + if ((ioctl(fd, EVIOCSFF, effect) < 0)) { + ALOGE("OFF: Failed to edit effect %d (%d): %s", id, errno, strerror(errno)); + } + } + } + } void debug(int fd) override { HwApiBase::debug(fd); } diff --git a/vibrator/cs40l26/VibMgrHwApi.h b/vibrator/cs40l26/VibMgrHwApi.h new file mode 100644 index 0000000..f263a18 --- /dev/null +++ b/vibrator/cs40l26/VibMgrHwApi.h @@ -0,0 +1,123 @@ +/* + * 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 +#include +#include + +#include + +#include "VibratorManager.h" +#include "utils.h" + +namespace aidl { +namespace android { +namespace hardware { +namespace vibrator { + +class VibMgrHwApi : public VibratorManager::HwApi { + private: + const uint32_t DEBUG_GPI_PIN = UINT16_MAX; + const uint32_t DEBUG_GPI_PIN_SHIFT = UINT16_MAX; + std::string mPropertyPrefix; + uint32_t mGPIOPin; + uint32_t mGPIOShift; + struct gpiohandle_request mRq; + + public: + static std::unique_ptr Create() { + auto hwapi = std::unique_ptr(new VibMgrHwApi()); + return hwapi; + } + bool getGPIO() override { + auto propertyPrefix = std::getenv("PROPERTY_PREFIX"); + + if (propertyPrefix != NULL) { + mPropertyPrefix = std::string(propertyPrefix); + } else { + ALOGE("GetGPIO: Failed get property prefix!"); + return false; + } + mGPIOPin = utils::getProperty(mPropertyPrefix + "gpio.num", DEBUG_GPI_PIN); + if (mGPIOPin == DEBUG_GPI_PIN) { + ALOGE("GetGPIO: Fail to get the GPIO num: %s", strerror(errno)); + return false; + } + mGPIOShift = utils::getProperty(mPropertyPrefix + "gpio.shift", DEBUG_GPI_PIN_SHIFT); + + if (mGPIOShift == DEBUG_GPI_PIN_SHIFT) { + ALOGE("GetGPIO: Fail to get the GPIO shift num: %s", strerror(errno)); + return false; + } + + return true; + } + bool initGPIO() override { + const auto gpio_dev = std::string() + "/dev/gpiochip" + std::to_string(mGPIOPin); + int fd = open(gpio_dev.c_str(), O_RDONLY); + if (fd < 0) { + ALOGE("InitGPIO: Unabled to open gpio dev: %s", strerror(errno)); + return false; + } + + mRq.lineoffsets[0] = mGPIOShift; + mRq.lines = 1; + mRq.flags = GPIOHANDLE_REQUEST_OUTPUT; + + int ret = ioctl(fd, GPIO_GET_LINEHANDLE_IOCTL, &mRq); + close(fd); + if (ret == -1) { + ALOGE("InitGPIO: Unable to line handle from ioctl : %s", strerror(errno)); + close(fd); + return false; + } + // Reset gpio status to LOW + struct gpiohandle_data data; + data.values[0] = 0; + + ret = ioctl(mRq.fd, GPIOHANDLE_SET_LINE_VALUES_IOCTL, &data); + if (ret == -1) { + ALOGE("InitGPIO: Unable to set line value using ioctl : %s", strerror(errno)); + close(mRq.fd); + return false; + } + return true; + } + bool setTrigger(bool value) override { + struct gpiohandle_data data; + data.values[0] = value; + + int ret = ioctl(mRq.fd, GPIOHANDLE_SET_LINE_VALUES_IOCTL, &data); + if (ret == -1) { + ALOGE("SetTrigger: Unable to set line value using ioctl : %s", strerror(errno)); + close(mRq.fd); + return false; + } + close(mRq.fd); + + return true; + } + void debug(int fd) override { ALOGD("Debug: %d", fd); } + + private: + VibMgrHwApi() { ALOGD("Constructor"); } +}; + +} // namespace vibrator +} // namespace hardware +} // namespace android +} // namespace aidl diff --git a/vibrator/cs40l26/Vibrator.cpp b/vibrator/cs40l26/Vibrator.cpp index 88914d8..765be3b 100644 --- a/vibrator/cs40l26/Vibrator.cpp +++ b/vibrator/cs40l26/Vibrator.cpp @@ -54,6 +54,7 @@ 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 auto POLLING_TIMEOUT_IN_SYNC = 100; 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. */ @@ -89,6 +90,16 @@ 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); +/* + * [15] Edge, 0:Falling, 1:Rising + * [14:12] GPI_NUM, 1:GPI1 (with CS40L26A, 1 is the only supported GPI) + * [8] BANK, 0:ROM, 1:RAM + * [7] USE_BUZZGEN, 0:Not buzzgen, 1:buzzgen + * [6:0] WAVEFORM_INDEX + * 0x9100 = 1001 0001 0000 0000: Rising + GPI1 + ROM + Not buzzgen + */ +static constexpr uint32_t GPIO_TRIGGER_CONFIG = 0x9100; + static uint16_t amplitudeToScale(float amplitude, float maximum) { float ratio = 100; /* Unit: % */ if (maximum != 0) @@ -341,7 +352,10 @@ Vibrator::Vibrator(std::unique_ptr hwapi, std::unique_ptr hwcal) mHwCal->getClickVolLevels(&mClickEffectVol); mHwCal->getLongVolLevels(&mLongEffectVol); } else { - ALOGD("Unsupported calibration version: %u!", calVer); + ALOGW("Unsupported calibration version! Using the default calibration value"); + mHwCal->getTickVolLevels(&mTickEffectVol); + mHwCal->getClickVolLevels(&mClickEffectVol); + mHwCal->getLongVolLevels(&mLongEffectVol); } mHwApi->setF0CompEnable(mHwCal->isF0CompEnabled()); @@ -364,7 +378,6 @@ Vibrator::Vibrator(std::unique_ptr hwapi, std::unique_ptr hwcal) } mSupportedPrimitives = defaultSupportedPrimitives; } - mHwApi->setMinOnOffInterval(MIN_ON_OFF_INTERVAL_US); } @@ -406,6 +419,8 @@ ndk::ScopedAStatus Vibrator::off() { ALOGE("Failed to clean up the composed effect %d", mActiveId); ret = false; } + + mHwApi->clearTrigBtn(mInputFd, &mFfEffects[mActiveId], mActiveId); } else { ALOGV("Vibrator is already off"); } @@ -425,7 +440,14 @@ ndk::ScopedAStatus Vibrator::off() { ndk::ScopedAStatus Vibrator::on(int32_t timeoutMs, const std::shared_ptr &callback) { + std::scoped_lock lock(mApiMutex); ATRACE_NAME("Vibrator::on"); + + if (isBusy()) { + ALOGD("Vibrator::on, isBusy"); + return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE); + } + if (timeoutMs > MAX_TIME_MS) { return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT); } @@ -445,6 +467,7 @@ ndk::ScopedAStatus Vibrator::on(int32_t timeoutMs, ndk::ScopedAStatus Vibrator::perform(Effect effect, EffectStrength strength, const std::shared_ptr &callback, int32_t *_aidl_return) { + std::scoped_lock lock(mApiMutex); ATRACE_NAME("Vibrator::perform"); return performEffect(effect, strength, callback, _aidl_return); } @@ -456,7 +479,9 @@ ndk::ScopedAStatus Vibrator::getSupportedEffects(std::vector *_aidl_retu } ndk::ScopedAStatus Vibrator::setAmplitude(float amplitude) { + std::scoped_lock lock(mApiMutex); ATRACE_NAME("Vibrator::setAmplitude"); + if (amplitude <= 0.0f || amplitude > 1.0f) { return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT); } @@ -470,7 +495,13 @@ ndk::ScopedAStatus Vibrator::setAmplitude(float amplitude) { } ndk::ScopedAStatus Vibrator::setExternalControl(bool enabled) { + std::scoped_lock lock(mApiMutex); ATRACE_NAME("Vibrator::setExternalControl"); + + if (isSynced()) { + return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION); + } + setGlobalAmplitude(enabled); if (mHasHapticAlsaDevice || mConfigHapticAlsaDeviceDone || hasHapticAlsaDevice()) { @@ -523,6 +554,7 @@ ndk::ScopedAStatus Vibrator::getPrimitiveDuration(CompositePrimitive primitive, ndk::ScopedAStatus Vibrator::compose(const std::vector &composite, const std::shared_ptr &callback) { + std::scoped_lock lock(mApiMutex); ATRACE_NAME("Vibrator::compose"); uint16_t size; uint16_t nextEffectDelay; @@ -635,6 +667,9 @@ ndk::ScopedAStatus Vibrator::on(uint32_t timeoutMs, uint32_t effectIndex, dspmem return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT); } int errorStatus; + if (isSynced()) { + mFfEffects[effectIndex].trigger.button = GPIO_TRIGGER_CONFIG | effectIndex; + } if (!mHwApi->uploadOwtEffect(mInputFd, ch->head, dspmem_chunk_bytes(ch), &mFfEffects[effectIndex], &effectIndex, &errorStatus)) { delete ch; @@ -647,22 +682,36 @@ ndk::ScopedAStatus Vibrator::on(uint32_t timeoutMs, uint32_t effectIndex, dspmem effectIndex == WAVEFORM_LONG_VIBRATION_EFFECT_INDEX) { /* Update duration for long/short vibration. */ mFfEffects[effectIndex].replay.length = static_cast(timeoutMs); + if (isSynced()) { + mFfEffects[effectIndex].trigger.button = GPIO_TRIGGER_CONFIG | effectIndex; + } if (!mHwApi->setFFEffect(mInputFd, &mFfEffects[effectIndex], static_cast(timeoutMs))) { ALOGE("Failed to edit effect %d (%d): %s", effectIndex, errno, strerror(errno)); return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE); } } - - const std::scoped_lock 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); + { + const std::scoped_lock lock(mActiveId_mutex); + mActiveId = effectIndex; + if (isSynced() && + (effectIndex == WAVEFORM_CLICK_INDEX || effectIndex == WAVEFORM_LIGHT_TICK_INDEX)) { + mFfEffects[effectIndex].trigger.button = GPIO_TRIGGER_CONFIG | effectIndex; + if (!mHwApi->setFFEffect(mInputFd, &mFfEffects[effectIndex], mFfEffects[effectIndex].replay.length)) { + ALOGE("Failed to edit effect %d (%d): %s", effectIndex, errno, strerror(errno)); + return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE); + } + } else if (!isSynced()) { + // /* 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(); } @@ -999,6 +1048,39 @@ bool Vibrator::isUnderExternalControl() { return mIsUnderExternalControl; } +// BnVibratorSync APIs + +Status Vibrator::prepareSynced(const sp &callback) { + std::scoped_lock lock(mApiMutex); + ATRACE_NAME("Vibrator::prepareSynced"); + + if (isBusy() || isSynced() || isUnderExternalControl()) { + ALOGE("Vibrator::prepareSynced, isBusy or isSynced"); + return Status::fromExceptionCode(EX_ILLEGAL_STATE); + } + + mSyncedCallback = callback; + + return Status::ok(); +} + +Status Vibrator::cancelSynced() { + std::scoped_lock lock(mApiMutex); + ATRACE_NAME("Vibrator::cancelSynced"); + + if (!isSynced()) { + ALOGE("Vibrator::cancelSynced, isSynced"); + return Status::fromExceptionCode(EX_ILLEGAL_STATE); + } + + off(); + mSyncedCallback = nullptr; + + return Status::ok(); +} + +// BnCInterface APIs + binder_status_t Vibrator::dump(int fd, const char **args, uint32_t numArgs) { if (fd < 0) { ALOGE("Called debug() with invalid fd."); @@ -1022,14 +1104,15 @@ binder_status_t Vibrator::dump(int fd, const char **args, uint32_t numArgs) { dprintf(fd, " FF effect:\n"); dprintf(fd, " Physical waveform:\n"); - dprintf(fd, "\tId\tIndex\tt ->\tt'\n"); + dprintf(fd, "\tId\tIndex\tt ->\tt'\ttrigger button\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, + dprintf(fd, "\t%d\t%d\t%d\t%d\t%X\n", mFfEffects[effectId].id, mFfEffects[effectId].u.periodic.custom_data[1], mEffectDurations[effectId], - mFfEffects[effectId].replay.length); + mFfEffects[effectId].replay.length, mFfEffects[effectId].trigger.button); } + dprintf(fd, " OWT waveform:\n"); - dprintf(fd, "\tId\tBytes\tData\n"); + dprintf(fd, "\tId\tBytes\tData\ttrigger button\n"); for (uint8_t effectId = WAVEFORM_MAX_PHYSICAL_INDEX; effectId < WAVEFORM_MAX_INDEX; effectId++) { uint32_t numBytes = mFfEffects[effectId].u.periodic.custom_len * 2; @@ -1042,7 +1125,8 @@ binder_status_t Vibrator::dump(int fd, const char **args, uint32_t numArgs) { i)) << " "; } - dprintf(fd, "\t%d\t%d\t{%s}\n", mFfEffects[effectId].id, numBytes, ss.str().c_str()); + dprintf(fd, "\t%d\t%d\t{%s}\t%X\n", mFfEffects[effectId].id, numBytes, ss.str().c_str(), + mFfEffects[effectId].trigger.button); } dprintf(fd, "\n"); @@ -1271,17 +1355,26 @@ ndk::ScopedAStatus Vibrator::performEffect(uint32_t effectIndex, uint32_t volLev } void Vibrator::waitForComplete(std::shared_ptr &&callback) { - if (!mHwApi->pollVibeState(VIBE_STATE_HAPTIC, POLLING_TIMEOUT)) { - ALOGW("Failed to get state \"Haptic\""); - } - mHwApi->pollVibeState(VIBE_STATE_STOPPED); + ALOGD("Callback status in waitForComplete(): mSync: %d, callBack: %d", isSynced(), + (callback != nullptr)); - const std::scoped_lock lock(mActiveId_mutex); - if ((mActiveId >= WAVEFORM_MAX_PHYSICAL_INDEX) && - (!mHwApi->eraseOwtEffect(mInputFd, mActiveId, &mFfEffects))) { - ALOGE("Failed to clean up the composed effect %d", mActiveId); + if (!mHwApi->pollVibeState(VIBE_STATE_HAPTIC, + (mSyncedCallback) ? POLLING_TIMEOUT_IN_SYNC : POLLING_TIMEOUT)) { + ALOGV("Fail to get state \"Haptic\""); + } + + mHwApi->pollVibeState(VIBE_STATE_STOPPED); + { + const std::scoped_lock lock(mActiveId_mutex); + if ((mActiveId >= WAVEFORM_MAX_PHYSICAL_INDEX) && + (!mHwApi->eraseOwtEffect(mInputFd, mActiveId, &mFfEffects))) { + ALOGE("Failed to clean up the composed effect %d", mActiveId); + } + + mHwApi->clearTrigBtn(mInputFd, &mFfEffects[mActiveId], mActiveId); + + mActiveId = -1; } - mActiveId = -1; if (callback) { auto ret = callback->onComplete(); @@ -1289,6 +1382,13 @@ void Vibrator::waitForComplete(std::shared_ptr &&callback) { ALOGE("Failed completion callback: %d", ret.getExceptionCode()); } } + if (mSyncedCallback) { + auto ret = mSyncedCallback->onComplete(); + if (!ret.isOk()) { + ALOGE("Failed completion mSyncedCallback: %d", ret.exceptionCode()); + } + mSyncedCallback = nullptr; + } } uint32_t Vibrator::intensityToVolLevel(float intensity, uint32_t effectIndex) { @@ -1321,6 +1421,16 @@ uint32_t Vibrator::intensityToVolLevel(float intensity, uint32_t effectIndex) { return volLevel; } +bool Vibrator::isBusy() { + auto timeout = std::chrono::seconds::zero(); + auto status = mAsyncHandle.wait_for(timeout); + return status != std::future_status::ready; +} + +bool Vibrator::isSynced() { + return (mSyncedCallback != nullptr); +} + } // namespace vibrator } // namespace hardware } // namespace android diff --git a/vibrator/cs40l26/Vibrator.h b/vibrator/cs40l26/Vibrator.h index 220c974..578a86a 100644 --- a/vibrator/cs40l26/Vibrator.h +++ b/vibrator/cs40l26/Vibrator.h @@ -17,6 +17,7 @@ #include #include +#include #include #include @@ -29,6 +30,10 @@ namespace android { namespace hardware { namespace vibrator { +using ::android::sp; +using ::android::binder::Status; +using ::android::hardware::vibrator::IVibratorSyncCallback; + class Vibrator : public BnVibrator { public: // APIs for interfacing with the kernel driver. @@ -80,6 +85,8 @@ class Vibrator : public BnVibrator { int *status) = 0; // Erase OWT waveform virtual bool eraseOwtEffect(int fd, int8_t effectIndex, std::vector *effect) = 0; + // Erase trigger button information + virtual void clearTrigBtn(int fd, struct ff_effect *effect, int8_t id) = 0; // Emit diagnostic information to the given file. virtual void debug(int fd) = 0; }; @@ -121,6 +128,7 @@ class Vibrator : public BnVibrator { public: Vibrator(std::unique_ptr hwapi, std::unique_ptr hwcal); + // BnVibrator APIs ndk::ScopedAStatus getCapabilities(int32_t *_aidl_return) override; ndk::ScopedAStatus off() override; ndk::ScopedAStatus on(int32_t timeoutMs, @@ -152,6 +160,11 @@ class Vibrator : public BnVibrator { ndk::ScopedAStatus composePwle(const std::vector &composite, const std::shared_ptr &callback) override; + // BnVibratorSync APIs + Status prepareSynced(const sp &callback); + Status cancelSynced(); + + // BnCInterface APIs binder_status_t dump(int fd, const char **args, uint32_t numArgs) override; private: @@ -182,6 +195,9 @@ class Vibrator : public BnVibrator { bool hasHapticAlsaDevice(); bool enableHapticPcmAmp(struct pcm **haptic_pcm, bool enable, int card, int device); + bool isBusy(); + bool isSynced(); + std::unique_ptr mHwApi; std::unique_ptr mHwCal; uint32_t mF0Offset; @@ -203,6 +219,9 @@ class Vibrator : public BnVibrator { uint32_t mSupportedPrimitivesBits = 0x0; std::vector mSupportedPrimitives; bool mConfigHapticAlsaDeviceDone{false}; + // prevent concurrent execution of IVibrator and IVibratorSync APIs + sp mSyncedCallback; + std::recursive_mutex mApiMutex; }; } // namespace vibrator diff --git a/vibrator/cs40l26/VibratorManager.cpp b/vibrator/cs40l26/VibratorManager.cpp new file mode 100644 index 0000000..3552c2f --- /dev/null +++ b/vibrator/cs40l26/VibratorManager.cpp @@ -0,0 +1,210 @@ +/* + * 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 "VibratorManager.h" + +#include +#include +#include + +#include + +namespace aidl { +namespace android { +namespace hardware { +namespace vibrator { + +using ::android::sp; +using ::android::binder::Status; +using ::android::hardware::vibrator::BnVibratorSyncCallback; + +class VibratorSyncCallback : public BnVibratorSyncCallback { + public: + Status onComplete() override { + mPromise.set_value(); + return Status::ok(); + } + auto getFuture() { return mPromise.get_future(); } + + private: + std::promise mPromise; +}; + +VibratorManager::VibratorManager(std::unique_ptr hwapi, + const std::vector &&vibrators) + : mHwApi(std::move(hwapi)), mVibrators(std::move(vibrators)), mAsyncHandle(std::async([] {})) { + mGPIOStatus = mHwApi->getGPIO(); +} + +// BnVibratorManager APIs + +ndk::ScopedAStatus VibratorManager::getCapabilities(int32_t *_aidl_return) { + ATRACE_NAME("VibratorManager::getCapabilities"); + int32_t ret = + IVibratorManager::CAP_SYNC | IVibratorManager::CAP_PREPARE_ON | + IVibratorManager::CAP_PREPARE_PERFORM | IVibratorManager::CAP_PREPARE_COMPOSE | + IVibratorManager::CAP_MIXED_TRIGGER_ON | IVibratorManager::CAP_MIXED_TRIGGER_PERFORM | + IVibratorManager::CAP_MIXED_TRIGGER_COMPOSE | IVibratorManager::CAP_TRIGGER_CALLBACK; + + *_aidl_return = ret; + return ndk::ScopedAStatus::ok(); +} + +ndk::ScopedAStatus VibratorManager::getVibratorIds(std::vector *_aidl_return) { + ATRACE_NAME("VibratorManager::getVibratorIds"); + _aidl_return->resize(mVibrators.size()); + std::iota(_aidl_return->begin(), _aidl_return->end(), 0); + return ndk::ScopedAStatus::ok(); +} + +ndk::ScopedAStatus VibratorManager::getVibrator(int vibratorId, + std::shared_ptr *_aidl_return) { + ATRACE_NAME("VibratorManager::getVibrator"); + if (!mGPIOStatus) { + ALOGE("GetVibrator: GPIO status error"); + return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE); + } + if (vibratorId >= mVibrators.size()) { + ALOGE("GetVibrator: wrong requested vibrator ID"); + return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT); + } + std::tie(*_aidl_return, std::ignore) = mVibrators.at(vibratorId); + return ndk::ScopedAStatus::ok(); +} + +ndk::ScopedAStatus VibratorManager::prepareSynced(const std::vector &ids) { + ATRACE_NAME("VibratorManager::prepareSynced"); + + if (!mGPIOStatus) { + ALOGE("GetVibrator: GPIO status error"); + return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE); + } + + if (ids.empty()) { + ALOGE("PrepareSynced: No vibrator could be synced"); + return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT); + } + + if (!mSyncContext.empty()) { + ALOGE("PrepareSynced: mSyncContext is not EMPTY!!!"); + return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE); + } + if (isBusy()) { + ALOGE("PrepareSynced: IS BUSY!!!"); + return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE); + } + + for (auto &id : ids) { + auto &[vib, ext] = mVibrators.at(id); + auto callback = sp::make(); + + ext->prepareSynced(callback); + + mSyncContext.emplace_back(id, callback->getFuture()); + } + if (mHwApi->initGPIO()) { + return ndk::ScopedAStatus::ok(); + } else { + ALOGE("PrepareSynced: GPIO status init fail"); + return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE); + } +} + +ndk::ScopedAStatus VibratorManager::triggerSynced( + const std::shared_ptr &callback) { + ATRACE_NAME("VibratorManager::triggerSynced"); + if (isBusy()) { + ALOGE("TriggerSynced isBusy"); + return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE); + } + + mHwApi->setTrigger(true); + + doAsync([=]() { + { + std::shared_lock lock(mContextMutex); + for (auto &[id, future] : mSyncContext) { + future.wait(); + } + } + { + std::unique_lock lock(mContextMutex); + mSyncContext.clear(); + } + if (callback) { + auto ret = callback->onComplete(); + if (!ret.isOk()) { + ALOGE("Failed completion callback: %d", ret.getExceptionCode()); + } + ALOGD("Callback in MANAGER onComplete()"); + } + }); + + return ndk::ScopedAStatus::ok(); +} + +ndk::ScopedAStatus VibratorManager::cancelSynced() { + ATRACE_NAME("VibratorManager::cancelSynced"); + + mHwApi->setTrigger(false); + { + std::shared_lock lock(mContextMutex); + for (auto &[id, future] : mSyncContext) { + auto &[vib, ext] = mVibrators.at(id); + ext->cancelSynced(); + } + } + { + std::unique_lock lock(mContextMutex); + mSyncContext.clear(); + } + mAsyncHandle.wait(); + + return ndk::ScopedAStatus::ok(); +} + +// BnCInterface APIs + +binder_status_t VibratorManager::dump(int fd, const char ** /*args*/, uint32_t /*numArgs*/) { + if (fd < 0) { + ALOGE("Called debug() with invalid fd."); + return STATUS_OK; + } + + mHwApi->debug(fd); + + fsync(fd); + + return STATUS_OK; +} + +// Private Methods + +template +void VibratorManager::doAsync(Func &&func, Args &&...args) { + mAsyncHandle = std::async(func, std::forward(args)...); +} + +bool VibratorManager::isBusy() { + auto timeout = std::chrono::seconds::zero(); + auto status = mAsyncHandle.wait_for(timeout); + return status != std::future_status::ready; +} + +} // namespace vibrator +} // namespace hardware +} // namespace android +} // namespace aidl diff --git a/vibrator/cs40l26/VibratorManager.h b/vibrator/cs40l26/VibratorManager.h new file mode 100644 index 0000000..e518a20 --- /dev/null +++ b/vibrator/cs40l26/VibratorManager.h @@ -0,0 +1,83 @@ +/* + * 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 +#include +#include + +#include +#include + +namespace aidl { +namespace android { +namespace hardware { +namespace vibrator { + +using ::android::hardware::vibrator::IVibratorSync; + +class VibratorManager : public BnVibratorManager { + public: + // APIs for interfacing with the kernel driver. + class HwApi { + public: + virtual ~HwApi() = default; + // Get the GPIO pin num and address shift information + virtual bool getGPIO() = 0; + // Init the GPIO function + virtual bool initGPIO() = 0; + // Trigger activation of the synchronized vibrators. + virtual bool setTrigger(bool value) = 0; + // Emit diagnostic information to the given file. + virtual void debug(int fd) = 0; + }; + + using VibratorTuple = std::tuple, ::android::sp>; + + public: + VibratorManager(std::unique_ptr hwapi, const std::vector &&vibrators); + + // BnVibratorManager APIs + ndk::ScopedAStatus getCapabilities(int32_t *_aidl_return); + ndk::ScopedAStatus getVibratorIds(std::vector *_aidl_return); + ndk::ScopedAStatus getVibrator(int vibratorId, std::shared_ptr *_aidl_return); + ndk::ScopedAStatus prepareSynced(const std::vector &ids) override; + ndk::ScopedAStatus triggerSynced(const std::shared_ptr &callback) override; + ndk::ScopedAStatus cancelSynced() override; + + // BnCInterface APIs + binder_status_t dump(int fd, const char **args, uint32_t numArgs) override; + + private: + template + void doAsync(Func &&func, Args &&...args); + bool isBusy(); + + private: + using SyncContext = std::tuple>; + + const std::unique_ptr mHwApi; + const std::vector mVibrators; + std::vector mSyncContext; + std::shared_mutex mContextMutex; + std::future mAsyncHandle; + bool mGPIOStatus; +}; + +} // namespace vibrator +} // namespace hardware +} // namespace android +} // namespace aidl diff --git a/vibrator/cs40l26/VibratorSync.cpp b/vibrator/cs40l26/VibratorSync.cpp new file mode 100644 index 0000000..437ee01 --- /dev/null +++ b/vibrator/cs40l26/VibratorSync.cpp @@ -0,0 +1,39 @@ +/* + * 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 "VibratorSync.h" + +namespace android { +namespace hardware { +namespace vibrator { + +VibratorSync::VibratorSync(std::shared_ptr vibrator) : mVibrator(vibrator) { + ALOGE("VibratorSync constructor"); +} + +// BnVibratorSync APIs + +binder::Status VibratorSync::prepareSynced(const sp &callback) { + return mVibrator->prepareSynced(callback); +} + +binder::Status VibratorSync::cancelSynced() { + return mVibrator->cancelSynced(); +} + +} // namespace vibrator +} // namespace hardware +} // namespace android diff --git a/vibrator/cs40l26/VibratorSync.h b/vibrator/cs40l26/VibratorSync.h new file mode 100644 index 0000000..fd9195a --- /dev/null +++ b/vibrator/cs40l26/VibratorSync.h @@ -0,0 +1,46 @@ +/* + * 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 + +#include +#include +#include + +#include "Vibrator.h" + +namespace android { +namespace hardware { +namespace vibrator { + +using ::aidl::android::hardware::vibrator::Vibrator; + +class VibratorSync : public BnVibratorSync { + public: + VibratorSync(std::shared_ptr vibrator); + + // BnVibratorSync APIs + binder::Status prepareSynced(const android::sp &callback) override; + binder::Status cancelSynced() override; + + private: + std::shared_ptr mVibrator; +}; + +} // namespace vibrator +} // namespace hardware +} // namespace android diff --git a/vibrator/cs40l26/aidl/Android.bp b/vibrator/cs40l26/aidl/Android.bp new file mode 100644 index 0000000..2c3b4b9 --- /dev/null +++ b/vibrator/cs40l26/aidl/Android.bp @@ -0,0 +1,13 @@ +aidl_interface { + name: "android.hardware.vibrator.cs40l26-private", + srcs: [ + "android/hardware/vibrator/*.aidl" + ], + backend: { + java: { + enabled: false, + }, + }, + unstable: true, + vendor_available: true, +} diff --git a/vibrator/cs40l26/aidl/android/hardware/vibrator/IVibratorSync.aidl b/vibrator/cs40l26/aidl/android/hardware/vibrator/IVibratorSync.aidl new file mode 100644 index 0000000..5371b13 --- /dev/null +++ b/vibrator/cs40l26/aidl/android/hardware/vibrator/IVibratorSync.aidl @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.hardware.vibrator; + +import android.hardware.vibrator.IVibratorSyncCallback; + +interface IVibratorSync { + void prepareSynced(in IVibratorSyncCallback callback); + void cancelSynced(); +} diff --git a/vibrator/cs40l26/aidl/android/hardware/vibrator/IVibratorSyncCallback.aidl b/vibrator/cs40l26/aidl/android/hardware/vibrator/IVibratorSyncCallback.aidl new file mode 100644 index 0000000..5281229 --- /dev/null +++ b/vibrator/cs40l26/aidl/android/hardware/vibrator/IVibratorSyncCallback.aidl @@ -0,0 +1,21 @@ +/* + * 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 android.hardware.vibrator; + +interface IVibratorSyncCallback { + oneway void onComplete(); +} diff --git a/vibrator/cs40l26/android.hardware.vibrator-service.cs40l26-stereo-private.rc b/vibrator/cs40l26/android.hardware.vibrator-service.cs40l26-stereo-private.rc new file mode 100644 index 0000000..01642dd --- /dev/null +++ b/vibrator/cs40l26/android.hardware.vibrator-service.cs40l26-stereo-private.rc @@ -0,0 +1,14 @@ +on property:vendor.all.modules.ready=1 + wait_for_prop init.svc.vendor.vibrator.cs40l26 running + wait_for_prop init.svc.vendor.vibrator.cs40l26-dual running + + enable vendor.vibrator.cs40l26-stereo + +service vendor.vibrator.cs40l26-stereo /vendor/bin/hw/android.hardware.vibrator-service.cs40l26-stereo-private + class hal + user root + group root + + setenv PROPERTY_PREFIX ro.vendor.vibrator.hal. + + disabled diff --git a/vibrator/cs40l26/android.hardware.vibrator-service.cs40l26-stereo-private.xml b/vibrator/cs40l26/android.hardware.vibrator-service.cs40l26-stereo-private.xml new file mode 100644 index 0000000..9056bd0 --- /dev/null +++ b/vibrator/cs40l26/android.hardware.vibrator-service.cs40l26-stereo-private.xml @@ -0,0 +1,7 @@ + + + android.hardware.vibrator + 2 + IVibratorManager/default + + diff --git a/vibrator/cs40l26/device-stereo.mk b/vibrator/cs40l26/device-stereo.mk index 08e4787..e4bf496 100644 --- a/vibrator/cs40l26/device-stereo.mk +++ b/vibrator/cs40l26/device-stereo.mk @@ -1,6 +1,7 @@ PRODUCT_PACKAGES += \ - android.hardware.vibrator-service.cs40l26 \ - android.hardware.vibrator-service.cs40l26-dual \ + android.hardware.vibrator-service.cs40l26-private \ + android.hardware.vibrator-service.cs40l26-dual-private \ + android.hardware.vibrator-service.cs40l26-stereo-private \ BOARD_SEPOLICY_DIRS += \ device/google/felix-sepolicy/vibrator/common \ diff --git a/vibrator/cs40l26/service-stereo.cpp b/vibrator/cs40l26/service-stereo.cpp new file mode 100644 index 0000000..cd05035 --- /dev/null +++ b/vibrator/cs40l26/service-stereo.cpp @@ -0,0 +1,82 @@ +/* + * 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 +#include +#include +#include +#include + +#include "VibMgrHwApi.h" +#include "VibratorManager.h" + +using ::aidl::android::hardware::vibrator::IVibrator; +using ::aidl::android::hardware::vibrator::VibMgrHwApi; +using ::aidl::android::hardware::vibrator::VibratorManager; +using ::android::ProcessState; +using ::android::String16; +using ::android::waitForService; +using ::android::hardware::vibrator::IVibratorSync; + +#if !defined(VIBRATOR_NAME) +#define VIBRATOR_NAME "default" +#endif + +using ndk::SharedRefBase; +using ndk::SpAIBinder; + +int main() { + auto hwapi = VibMgrHwApi::Create(); + if (!hwapi) { + return EXIT_FAILURE; + } + + const std::string vibratorInstances[] = { + "default", + "dual", + }; + std::vector vibrators; + + ProcessState::initWithDriver("/dev/vndbinder"); + + for (auto &instance : vibratorInstances) { + const auto svcName = std::string() + IVibrator::descriptor + "/" + instance; + const auto extName = std::stringstream() << IVibratorSync::descriptor << "/" << instance; + + SpAIBinder svcBinder; + svcBinder = SpAIBinder(AServiceManager_getService(svcName.c_str())); + auto svc = IVibrator::fromBinder(svcBinder); + + auto ext = waitForService(String16(extName.str().c_str())); + + vibrators.emplace_back(svc, ext); + } + + auto mgr = ndk::SharedRefBase::make(std::move(hwapi), std::move(vibrators)); + binder_status_t status; + + const std::string mgrInst = std::string() + VibratorManager::descriptor + "/" VIBRATOR_NAME; + status = AServiceManager_addService(mgr->asBinder().get(), mgrInst.c_str()); + LOG_ALWAYS_FATAL_IF(status != STATUS_OK); + + // Only used for callbacks + ProcessState::self()->setThreadPoolMaxThreadCount(1); + ProcessState::self()->startThreadPool(); + + ABinderProcess_setThreadPoolMaxThreadCount(0); + ABinderProcess_joinThreadPool(); + + return EXIT_FAILURE; // should not reach +} diff --git a/vibrator/cs40l26/service.cpp b/vibrator/cs40l26/service.cpp index 27173d9..fd76b19 100644 --- a/vibrator/cs40l26/service.cpp +++ b/vibrator/cs40l26/service.cpp @@ -21,6 +21,7 @@ #include "Hardware.h" #include "Vibrator.h" +#include "VibratorSync.h" using ::aidl::android::hardware::vibrator::HwApi; using ::aidl::android::hardware::vibrator::HwCal; @@ -29,6 +30,7 @@ using ::android::defaultServiceManager; using ::android::ProcessState; using ::android::sp; using ::android::String16; +using ::android::hardware::vibrator::VibratorSync; #if !defined(VIBRATOR_NAME) #define VIBRATOR_NAME "default" @@ -39,8 +41,13 @@ int main() { std::make_unique()); const auto svcName = std::string() + svc->descriptor + "/" + VIBRATOR_NAME; + auto ext = sp::make(svc); + const auto extName = std::stringstream() << ext->descriptor << "/" << VIBRATOR_NAME; + ProcessState::initWithDriver("/dev/vndbinder"); + defaultServiceManager()->addService(String16(extName.str().c_str()), ext); + auto svcBinder = svc->asBinder(); binder_status_t status = AServiceManager_addService(svcBinder.get(), svcName.c_str()); LOG_ALWAYS_FATAL_IF(status != STATUS_OK); diff --git a/vibrator/cs40l26/tests/mocks.h b/vibrator/cs40l26/tests/mocks.h index 21466a0..e497f2a 100644 --- a/vibrator/cs40l26/tests/mocks.h +++ b/vibrator/cs40l26/tests/mocks.h @@ -43,6 +43,7 @@ class MockApi : public ::aidl::android::hardware::vibrator::Vibrator::HwApi { 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 *effect)); + MOCK_METHOD3(clearTrigBtn, void(int fd, struct ff_effect *effect, int8_t index)); MOCK_METHOD1(debug, void(int fd)); ~MockApi() override { destructor(); }; From 3f32520d2fa7faa589ff049315d616b7775c7626 Mon Sep 17 00:00:00 2001 From: Chase Wu Date: Wed, 12 Oct 2022 23:17:03 +0800 Subject: [PATCH 5/5] [DO NOT MERGE] cs40l26: Enable vibrator manager feature This patch remove the old dual design and add it into the original vibrator HALs'rc file. Also, this patch dynamically gets the service name from rc file. Bug: 181615889 Test: Manual type and trigger a long/short vibration Test: cmd vibrator_manager synced xxxx Test: cmd vibrator_manager sequential -v 0 xxxx Signed-off-by: Chase Wu Change-Id: I7a5dd65c67e01a008f8d2c675dc7b96599469622 --- device-felix.mk | 5 +- vibrator/cs40l26/Android.bp | 22 --------- vibrator/cs40l26/VibMgrHwApi.h | 4 +- vibrator/cs40l26/Vibrator.cpp | 18 +++++-- vibrator/cs40l26/VibratorManager.cpp | 12 +++++ ...e.vibrator-service.cs40l26-dual-private.rc | 47 ------------------- ....vibrator-service.cs40l26-dual-private.xml | 7 --- ...rdware.vibrator-service.cs40l26-private.rc | 41 ++++++++++++++++ ...dware.vibrator-service.cs40l26-private.xml | 5 ++ ...vibrator-service.cs40l26-stereo-private.rc | 1 + vibrator/cs40l26/device-stereo.mk | 7 ++- vibrator/cs40l26/service.cpp | 18 ++++--- 12 files changed, 95 insertions(+), 92 deletions(-) delete mode 100644 vibrator/cs40l26/android.hardware.vibrator-service.cs40l26-dual-private.rc delete mode 100644 vibrator/cs40l26/android.hardware.vibrator-service.cs40l26-dual-private.xml diff --git a/device-felix.mk b/device-felix.mk index 2db1ac9..6658410 100644 --- a/device-felix.mk +++ b/device-felix.mk @@ -190,7 +190,10 @@ PRODUCT_VENDOR_PROPERTIES += \ # Vibrator HAL PRODUCT_PRODUCT_PROPERTIES +=\ - ro.vendor.vibrator.hal.long.frequency.shift=0 + ro.vendor.vibrator.hal.long.frequency.shift=0 \ + ro.vendor.vibrator.hal.chirp.enabled=1 \ + ro.vendor.vibrator.hal.gpio.num=44 \ + ro.vendor.vibrator.hal.gpio.shift=2 ACTUATOR_MODEL := luxshare_ict_lt_xlra1906d # Fingerprint diff --git a/vibrator/cs40l26/Android.bp b/vibrator/cs40l26/Android.bp index 7857580..9c38418 100644 --- a/vibrator/cs40l26/Android.bp +++ b/vibrator/cs40l26/Android.bp @@ -80,25 +80,6 @@ cc_binary { shared_libs: [ "android.hardware.vibrator-impl.cs40l26-private", ], - cflags: [ - "-DLOG_TAG=\"android.hardware.vibrator-cs40l26-private\"", - ], - proprietary: true, -} - -cc_binary { - name: "android.hardware.vibrator-service.cs40l26-dual-private", - defaults: ["VibratorHalCs40l26BinaryDefaultsPrivate"], - init_rc: ["android.hardware.vibrator-service.cs40l26-dual-private.rc"], - vintf_fragments: ["android.hardware.vibrator-service.cs40l26-dual-private.xml"], - srcs: ["service.cpp"], - shared_libs: [ - "android.hardware.vibrator-impl.cs40l26-private", - ], - cflags: [ - "-DVIBRATOR_NAME=\"dual\"", - "-DLOG_TAG=\"android.hardware.vibrator-cs40l26-dual-private\"", - ], proprietary: true, } @@ -111,8 +92,5 @@ cc_binary { shared_libs: [ "android.hardware.vibrator-impl.cs40l26-private", ], - cflags: [ - "-DLOG_TAG=\"android.hardware.vibrator-cs40l26-stereo-private\"", - ], proprietary: true, } diff --git a/vibrator/cs40l26/VibMgrHwApi.h b/vibrator/cs40l26/VibMgrHwApi.h index f263a18..be6e062 100644 --- a/vibrator/cs40l26/VibMgrHwApi.h +++ b/vibrator/cs40l26/VibMgrHwApi.h @@ -54,13 +54,13 @@ class VibMgrHwApi : public VibratorManager::HwApi { } mGPIOPin = utils::getProperty(mPropertyPrefix + "gpio.num", DEBUG_GPI_PIN); if (mGPIOPin == DEBUG_GPI_PIN) { - ALOGE("GetGPIO: Fail to get the GPIO num: %s", strerror(errno)); + ALOGE("GetGPIO: Failed to get the GPIO num: %s", strerror(errno)); return false; } mGPIOShift = utils::getProperty(mPropertyPrefix + "gpio.shift", DEBUG_GPI_PIN_SHIFT); if (mGPIOShift == DEBUG_GPI_PIN_SHIFT) { - ALOGE("GetGPIO: Fail to get the GPIO shift num: %s", strerror(errno)); + ALOGE("GetGPIO: Failed to get the GPIO shift num: %s", strerror(errno)); return false; } diff --git a/vibrator/cs40l26/Vibrator.cpp b/vibrator/cs40l26/Vibrator.cpp index 765be3b..b934168 100644 --- a/vibrator/cs40l26/Vibrator.cpp +++ b/vibrator/cs40l26/Vibrator.cpp @@ -100,6 +100,18 @@ static constexpr float PWLE_BW_MAP_SIZE = */ static constexpr uint32_t GPIO_TRIGGER_CONFIG = 0x9100; +const char *kHAPNAME = std::getenv("HAPTIC_NAME"); +#undef ALOGV +#define ALOGV(...) ((void)ALOG(LOG_VERBOSE, kHAPNAME, __VA_ARGS__)) +#undef ALOGD +#define ALOGD(...) ((void)ALOG(LOG_DEBUG, kHAPNAME, __VA_ARGS__)) +#undef ALOGI +#define ALOGI(...) ((void)ALOG(LOG_INFO, kHAPNAME, __VA_ARGS__)) +#undef ALOGW +#define ALOGW(...) ((void)ALOG(LOG_WARN, kHAPNAME, __VA_ARGS__)) +#undef ALOGE +#define ALOGE(...) ((void)ALOG(LOG_ERROR, kHAPNAME, __VA_ARGS__)) + static uint16_t amplitudeToScale(float amplitude, float maximum) { float ratio = 100; /* Unit: % */ if (maximum != 0) @@ -248,7 +260,7 @@ Vibrator::Vibrator(std::unique_ptr hwapi, std::unique_ptr hwcal) 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)); + ALOGE("Failed 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)); @@ -278,7 +290,7 @@ Vibrator::Vibrator(std::unique_ptr hwapi, std::unique_ptr hwcal) } if (!mInputFd.ok()) { - ALOGE("Fail to get an input event with name %s", inputEventName); + ALOGE("Failed to get an input event with name %s", inputEventName); } } else { ALOGE("The input name %s is not cs40l26_input or cs40l26_dual_input", inputEventName); @@ -1360,7 +1372,7 @@ void Vibrator::waitForComplete(std::shared_ptr &&callback) { if (!mHwApi->pollVibeState(VIBE_STATE_HAPTIC, (mSyncedCallback) ? POLLING_TIMEOUT_IN_SYNC : POLLING_TIMEOUT)) { - ALOGV("Fail to get state \"Haptic\""); + ALOGV("Failed to get state \"Haptic\""); } mHwApi->pollVibeState(VIBE_STATE_STOPPED); diff --git a/vibrator/cs40l26/VibratorManager.cpp b/vibrator/cs40l26/VibratorManager.cpp index 3552c2f..7c826ee 100644 --- a/vibrator/cs40l26/VibratorManager.cpp +++ b/vibrator/cs40l26/VibratorManager.cpp @@ -27,6 +27,18 @@ namespace android { namespace hardware { namespace vibrator { +const char *kHAPMGRNAME = std::getenv("HAPTIC_MGR_NAME"); +#undef ALOGV +#define ALOGV(...) ((void)ALOG(LOG_VERBOSE, kHAPMGRNAME, __VA_ARGS__)) +#undef ALOGD +#define ALOGD(...) ((void)ALOG(LOG_DEBUG, kHAPMGRNAME, __VA_ARGS__)) +#undef ALOGI +#define ALOGI(...) ((void)ALOG(LOG_INFO, kHAPMGRNAME, __VA_ARGS__)) +#undef ALOGW +#define ALOGW(...) ((void)ALOG(LOG_WARN, kHAPMGRNAME, __VA_ARGS__)) +#undef ALOGE +#define ALOGE(...) ((void)ALOG(LOG_ERROR, kHAPMGRNAME, __VA_ARGS__)) + using ::android::sp; using ::android::binder::Status; using ::android::hardware::vibrator::BnVibratorSyncCallback; diff --git a/vibrator/cs40l26/android.hardware.vibrator-service.cs40l26-dual-private.rc b/vibrator/cs40l26/android.hardware.vibrator-service.cs40l26-dual-private.rc deleted file mode 100644 index 07a43ff..0000000 --- a/vibrator/cs40l26/android.hardware.vibrator-service.cs40l26-dual-private.rc +++ /dev/null @@ -1,47 +0,0 @@ -on property:vendor.all.modules.ready=1 - wait /sys/bus/i2c/devices/i2c-cs40l26a-dual/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_dual.cal - chown system system /mnt/vendor/persist/haptics - chown system system /mnt/vendor/persist/haptics/cs40l26_dual.cal - - chown system system /sys/bus/i2c/devices/i2c-cs40l26a-dual/calibration/f0_stored - chown system system /sys/bus/i2c/devices/i2c-cs40l26a-dual/calibration/q_stored - chown system system /sys/bus/i2c/devices/i2c-cs40l26a-dual/calibration/redc_stored - chown system system /sys/bus/i2c/devices/i2c-cs40l26a-dual/default/vibe_state - chown system system /sys/bus/i2c/devices/i2c-cs40l26a-dual/default/num_waves - chown system system /sys/bus/i2c/devices/i2c-cs40l26a-dual/default/f0_offset - chown system system /sys/bus/i2c/devices/i2c-cs40l26a-dual/default/owt_free_space - chown system system /sys/bus/i2c/devices/i2c-cs40l26a-dual/default/f0_comp_enable - chown system system /sys/bus/i2c/devices/i2c-cs40l26a-dual/default/redc_comp_enable - chown system system /sys/bus/i2c/devices/i2c-cs40l26a-dual/default/delay_before_stop_playback_us - - enable vendor.vibrator.cs40l26-dual - -service vendor.vibrator.cs40l26-dual /vendor/bin/hw/android.hardware.vibrator-service.cs40l26-dual-private - class hal - user system - group system input - - setenv INPUT_EVENT_NAME cs40l26_dual_input - setenv INPUT_EVENT_PATH /dev/input/event* - setenv PROPERTY_PREFIX ro.vendor.vibrator.hal. - setenv CALIBRATION_FILEPATH /mnt/vendor/persist/haptics/cs40l26_dual.cal - - setenv HWAPI_PATH_PREFIX /sys/bus/i2c/devices/i2c-cs40l26a-dual/ - 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 diff --git a/vibrator/cs40l26/android.hardware.vibrator-service.cs40l26-dual-private.xml b/vibrator/cs40l26/android.hardware.vibrator-service.cs40l26-dual-private.xml deleted file mode 100644 index 1bd3e7e..0000000 --- a/vibrator/cs40l26/android.hardware.vibrator-service.cs40l26-dual-private.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - android.hardware.vibrator - 2 - IVibrator/dual - - diff --git a/vibrator/cs40l26/android.hardware.vibrator-service.cs40l26-private.rc b/vibrator/cs40l26/android.hardware.vibrator-service.cs40l26-private.rc index bbc0135..b12c359 100644 --- a/vibrator/cs40l26/android.hardware.vibrator-service.cs40l26-private.rc +++ b/vibrator/cs40l26/android.hardware.vibrator-service.cs40l26-private.rc @@ -4,27 +4,41 @@ on property:vendor.all.modules.ready=1 mkdir /mnt/vendor/persist/haptics 0770 system system chmod 770 /mnt/vendor/persist/haptics chmod 440 /mnt/vendor/persist/haptics/cs40l26.cal + chmod 440 /mnt/vendor/persist/haptics/cs40l26_dual.cal chown system system /mnt/vendor/persist/haptics chown system system /mnt/vendor/persist/haptics/cs40l26.cal + chown system system /mnt/vendor/persist/haptics/cs40l26_dual.cal chown system system /sys/bus/i2c/devices/i2c-cs40l26a/calibration/f0_stored + chown system system /sys/bus/i2c/devices/i2c-cs40l26a-dual/calibration/f0_stored chown system system /sys/bus/i2c/devices/i2c-cs40l26a/calibration/q_stored + chown system system /sys/bus/i2c/devices/i2c-cs40l26a-dual/calibration/q_stored chown system system /sys/bus/i2c/devices/i2c-cs40l26a/calibration/redc_stored + chown system system /sys/bus/i2c/devices/i2c-cs40l26a-dual/calibration/redc_stored chown system system /sys/bus/i2c/devices/i2c-cs40l26a/default/vibe_state + chown system system /sys/bus/i2c/devices/i2c-cs40l26a-dual/default/vibe_state chown system system /sys/bus/i2c/devices/i2c-cs40l26a/default/num_waves + chown system system /sys/bus/i2c/devices/i2c-cs40l26a-dual/default/num_waves chown system system /sys/bus/i2c/devices/i2c-cs40l26a/default/f0_offset + chown system system /sys/bus/i2c/devices/i2c-cs40l26a-dual/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-dual/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-dual/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-dual/default/redc_comp_enable chown system system /sys/bus/i2c/devices/i2c-cs40l26a/default/delay_before_stop_playback_us + chown system system /sys/bus/i2c/devices/i2c-cs40l26a-dual/default/delay_before_stop_playback_us enable vendor.vibrator.cs40l26 + enable vendor.vibrator.cs40l26-dual service vendor.vibrator.cs40l26 /vendor/bin/hw/android.hardware.vibrator-service.cs40l26-private class hal user system group system input + setenv HAPTIC_NAME HapticsBase setenv INPUT_EVENT_NAME cs40l26_input setenv INPUT_EVENT_PATH /dev/input/event* setenv PROPERTY_PREFIX ro.vendor.vibrator.hal. @@ -45,3 +59,30 @@ service vendor.vibrator.cs40l26 /vendor/bin/hw/android.hardware.vibrator-service " disabled + +service vendor.vibrator.cs40l26-dual /vendor/bin/hw/android.hardware.vibrator-service.cs40l26-private + class hal + user system + group system input + + setenv HAPTIC_NAME HapticsFlip + setenv INPUT_EVENT_NAME cs40l26_dual_input + setenv INPUT_EVENT_PATH /dev/input/event* + setenv PROPERTY_PREFIX ro.vendor.vibrator.hal. + setenv CALIBRATION_FILEPATH /mnt/vendor/persist/haptics/cs40l26_dual.cal + + setenv HWAPI_PATH_PREFIX /sys/bus/i2c/devices/i2c-cs40l26a-dual/ + 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 diff --git a/vibrator/cs40l26/android.hardware.vibrator-service.cs40l26-private.xml b/vibrator/cs40l26/android.hardware.vibrator-service.cs40l26-private.xml index 4db8f8c..38df26a 100644 --- a/vibrator/cs40l26/android.hardware.vibrator-service.cs40l26-private.xml +++ b/vibrator/cs40l26/android.hardware.vibrator-service.cs40l26-private.xml @@ -4,4 +4,9 @@ 2 IVibrator/default + + android.hardware.vibrator + 2 + IVibrator/dual + diff --git a/vibrator/cs40l26/android.hardware.vibrator-service.cs40l26-stereo-private.rc b/vibrator/cs40l26/android.hardware.vibrator-service.cs40l26-stereo-private.rc index 01642dd..7137c6a 100644 --- a/vibrator/cs40l26/android.hardware.vibrator-service.cs40l26-stereo-private.rc +++ b/vibrator/cs40l26/android.hardware.vibrator-service.cs40l26-stereo-private.rc @@ -9,6 +9,7 @@ service vendor.vibrator.cs40l26-stereo /vendor/bin/hw/android.hardware.vibrator- user root group root + setenv HAPTIC_MGR_NAME HapticsMgr setenv PROPERTY_PREFIX ro.vendor.vibrator.hal. disabled diff --git a/vibrator/cs40l26/device-stereo.mk b/vibrator/cs40l26/device-stereo.mk index e4bf496..3bc187e 100644 --- a/vibrator/cs40l26/device-stereo.mk +++ b/vibrator/cs40l26/device-stereo.mk @@ -1,8 +1,7 @@ PRODUCT_PACKAGES += \ android.hardware.vibrator-service.cs40l26-private \ - android.hardware.vibrator-service.cs40l26-dual-private \ - android.hardware.vibrator-service.cs40l26-stereo-private \ + android.hardware.vibrator-service.cs40l26-stereo-private BOARD_SEPOLICY_DIRS += \ - device/google/felix-sepolicy/vibrator/common \ - device/google/felix-sepolicy/vibrator/cs40l26 + hardware/google/pixel-sepolicy/vibrator/common \ + hardware/google/pixel-sepolicy/vibrator/cs40l26 diff --git a/vibrator/cs40l26/service.cpp b/vibrator/cs40l26/service.cpp index fd76b19..8bcd623 100644 --- a/vibrator/cs40l26/service.cpp +++ b/vibrator/cs40l26/service.cpp @@ -32,17 +32,23 @@ using ::android::sp; using ::android::String16; using ::android::hardware::vibrator::VibratorSync; -#if !defined(VIBRATOR_NAME) -#define VIBRATOR_NAME "default" -#endif - int main() { + const char *inputEventName = std::getenv("INPUT_EVENT_NAME"); + std::string vibName = ""; + if (strstr(inputEventName, "cs40l26_input") != nullptr) { + vibName.assign("default"); + } else if (strstr(inputEventName, "cs40l26_dual_input") != nullptr) { + vibName.assign("dual"); + } else { + ALOGE("Failed to init vibrator HAL"); + return EXIT_FAILURE; // should not reach + } auto svc = ndk::SharedRefBase::make(std::make_unique(), std::make_unique()); - const auto svcName = std::string() + svc->descriptor + "/" + VIBRATOR_NAME; + const auto svcName = std::string() + svc->descriptor + "/" + vibName; auto ext = sp::make(svc); - const auto extName = std::stringstream() << ext->descriptor << "/" << VIBRATOR_NAME; + const auto extName = std::stringstream() << ext->descriptor << "/" << vibName; ProcessState::initWithDriver("/dev/vndbinder");