From 0db068b63c425a82ec6edcc348d29d9aeca8ba5f Mon Sep 17 00:00:00 2001 From: Chris Paulo Date: Wed, 2 Nov 2022 22:21:50 +0000 Subject: [PATCH] device/vibrator: Add contextual haptics feature Add capability for vibrator HAL to detect whether the device is face-up and adjust/scale haptic alerts to avoid loud and startling buzzing when there is no case on the device. Added global compile-time disable that can be set in the environment. Bug: 198239103 Test: Verified tests and functionality Change-Id: I6b2355acb7fa5e0323b8eca6327bb19ac42a2c56 Signed-off-by: Chris Paulo --- conf/init.lynx.rc | 7 + device-lynx.mk | 9 +- vibrator/Android.bp | 12 +- vibrator/OWNERS | 1 + vibrator/common/Android.bp | 2 +- vibrator/cs40l26/Android.bp | 81 ++++--- vibrator/cs40l26/CapoDetector.cpp | 216 ++++++++++++++++++ vibrator/cs40l26/Hardware.h | 16 ++ vibrator/cs40l26/Vibrator.cpp | 136 ++++++++++- vibrator/cs40l26/Vibrator.h | 26 ++- ....vibrator-service.cs40l26-private-lynx.rc} | 4 +- ...vibrator-service.cs40l26-private-lynx.xml} | 0 vibrator/cs40l26/device.mk | 2 +- vibrator/cs40l26/inc/CapoDetector.h | 107 +++++++++ vibrator/cs40l26/proto/capo.proto | 148 ++++++++++++ vibrator/cs40l26/tests/Android.bp | 4 +- vibrator/cs40l26/tests/mocks.h | 5 + vibrator/cs40l26/tests/test-vibrator.cpp | 10 + 18 files changed, 730 insertions(+), 56 deletions(-) create mode 100644 vibrator/cs40l26/CapoDetector.cpp rename vibrator/cs40l26/{android.hardware.vibrator-service.cs40l26.rc => android.hardware.vibrator-service.cs40l26-private-lynx.rc} (96%) rename vibrator/cs40l26/{android.hardware.vibrator-service.cs40l26.xml => android.hardware.vibrator-service.cs40l26-private-lynx.xml} (100%) create mode 100644 vibrator/cs40l26/inc/CapoDetector.h create mode 100644 vibrator/cs40l26/proto/capo.proto diff --git a/conf/init.lynx.rc b/conf/init.lynx.rc index 1da8ea7..dbc4f13 100644 --- a/conf/init.lynx.rc +++ b/conf/init.lynx.rc @@ -110,3 +110,10 @@ on override-sf-uclamp # it should be written by the system init. on property:ro.boot.hardware.sku=G82U8 setprop audio.camerasound.force true + +# Route vibrator.adaptive_haptics.enabled to persist +on property:vibrator.adaptive_haptics.enabled=0 + setprop persist.vendor.vibrator.hal.context.enable false + +on property:vibrator.adaptive_haptics.enabled=1 + setprop persist.vendor.vibrator.hal.context.enable true diff --git a/device-lynx.mk b/device-lynx.mk index b69e7af..5bf8123 100644 --- a/device-lynx.mk +++ b/device-lynx.mk @@ -28,7 +28,7 @@ DEVICE_PACKAGE_OVERLAYS += device/google/lynx/lynx/overlay include device/google/lynx/audio/lynx/audio-tables.mk include device/google/gs201/device-shipping-common.mk -include hardware/google/pixel/vibrator/cs40l26/device.mk +include device/google/lynx/vibrator/cs40l26/device.mk # go/lyric-soong-variables $(call soong_config_set,lyric,camera_hardware,lynx) @@ -154,7 +154,12 @@ endif PRODUCT_VENDOR_PROPERTIES += \ ro.vendor.vibrator.hal.supported_primitives=243 \ ro.vendor.vibrator.hal.f0.comp.enabled=1 \ - ro.vendor.vibrator.hal.redc.comp.enabled=0 + ro.vendor.vibrator.hal.redc.comp.enabled=0 \ + persist.vendor.vibrator.hal.context.enable=false \ + persist.vendor.vibrator.hal.context.scale=40 \ + persist.vendor.vibrator.hal.context.fade=true \ + persist.vendor.vibrator.hal.context.cooldowntime=1600 \ + persist.vendor.vibrator.hal.context.settlingtime=5000 # Trusty liboemcrypto.so PRODUCT_SOONG_NAMESPACES += vendor/google_devices/lynx/prebuilts diff --git a/vibrator/Android.bp b/vibrator/Android.bp index dff6816..c6da071 100644 --- a/vibrator/Android.bp +++ b/vibrator/Android.bp @@ -18,10 +18,10 @@ package { } cc_defaults { - name: "PixelVibratorDefaults", + name: "PixelVibratorDefaultsPrivateLynx", relative_install_path: "hw", static_libs: [ - "PixelVibratorCommon", + "PixelVibratorCommonPrivateLynx", ], shared_libs: [ "libbase", @@ -34,16 +34,16 @@ cc_defaults { } cc_defaults { - name: "PixelVibratorBinaryDefaults", - defaults: ["PixelVibratorDefaults"], + name: "PixelVibratorBinaryDefaultsPrivateLynx", + defaults: ["PixelVibratorDefaultsPrivateLynx"], shared_libs: [ "android.hardware.vibrator-V2-ndk", ], } cc_defaults { - name: "PixelVibratorTestDefaults", - defaults: ["PixelVibratorDefaults"], + name: "PixelVibratorTestDefaultsPrivateLynx", + defaults: ["PixelVibratorDefaultsPrivateLynx"], static_libs: [ "android.hardware.vibrator-V2-ndk", ], diff --git a/vibrator/OWNERS b/vibrator/OWNERS index 5d4a9c3..899224c 100644 --- a/vibrator/OWNERS +++ b/vibrator/OWNERS @@ -1,3 +1,4 @@ chasewu@google.com michaelwr@google.com taikuo@google.com +chrispaulo@google.com diff --git a/vibrator/common/Android.bp b/vibrator/common/Android.bp index 04fbc4d..b2a6d48 100644 --- a/vibrator/common/Android.bp +++ b/vibrator/common/Android.bp @@ -18,7 +18,7 @@ package { } cc_library { - name: "PixelVibratorCommon", + name: "PixelVibratorCommonPrivateLynx", srcs: [ "HardwareBase.cpp", ], diff --git a/vibrator/cs40l26/Android.bp b/vibrator/cs40l26/Android.bp index 53764de..f60189d 100644 --- a/vibrator/cs40l26/Android.bp +++ b/vibrator/cs40l26/Android.bp @@ -1,5 +1,5 @@ // -// Copyright (C) 2021 The Android Open Source Project +// 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. @@ -18,7 +18,7 @@ package { } cc_defaults { - name: "android.hardware.vibrator-defaults.cs40l26", + name: "android.hardware.vibrator-defaults.cs40l26-private-lynx", cflags: [ "-DATRACE_TAG=(ATRACE_TAG_VIBRATOR | ATRACE_TAG_HAL)", "-DLOG_TAG=\"android.hardware.vibrator-cs40l26\"", @@ -29,10 +29,10 @@ cc_defaults { } cc_defaults { - name: "VibratorHalCs40l26BinaryDefaults", + name: "VibratorHalCs40l26BinaryDefaultsPrivateLynx", defaults: [ - "PixelVibratorBinaryDefaults", - "android.hardware.vibrator-defaults.cs40l26", + "PixelVibratorBinaryDefaultsPrivateLynx", + "android.hardware.vibrator-defaults.cs40l26-private-lynx", ], include_dirs: [ "external/tinyalsa/include", @@ -44,43 +44,64 @@ cc_defaults { } cc_defaults { - name: "VibratorHalCs40l26TestDefaults", + name: "VibratorHalCs40l26TestDefaultsPrivateLynx", defaults: [ - "PixelVibratorTestDefaults", - "android.hardware.vibrator-defaults.cs40l26", + "PixelVibratorTestDefaultsPrivateLynx", + "android.hardware.vibrator-defaults.cs40l26-private-lynx", ], static_libs: [ - "android.hardware.vibrator-impl.cs40l26", "libtinyalsa", ], + shared_libs: ["android.hardware.vibrator-impl.cs40l26-private-lynx"], } -cc_library { - name: "android.hardware.vibrator-impl.cs40l26", - defaults: ["VibratorHalCs40l26BinaryDefaults"], - srcs: ["Vibrator.cpp"], - export_include_dirs: ["."], +cc_library_shared { + name: "libvibecapo_proto_lynx", + vendor_available: true, + owner: "google", + srcs: [ + "proto/capo.proto", + ], + shared_libs: [ + "libprotobuf-cpp-full", + ], + export_include_dirs: [ + "inc", + ], + proto: { + type: "lite", + export_proto_headers: true, + }, +} + +cc_library_shared { + name: "android.hardware.vibrator-impl.cs40l26-private-lynx", + defaults: ["VibratorHalCs40l26BinaryDefaultsPrivateLynx"], + srcs: [ + "Vibrator.cpp", + "CapoDetector.cpp", + ], + static_libs: [ + "chre_client", + ], + shared_libs: [ + "libvibecapo_proto_lynx", + "libprotobuf-cpp-full", + ], + export_include_dirs: [ + ".", + "inc", + ], vendor_available: true, visibility: [":__subpackages__"], } cc_binary { - name: "android.hardware.vibrator-service.cs40l26", - defaults: ["VibratorHalCs40l26BinaryDefaults"], - init_rc: ["android.hardware.vibrator-service.cs40l26.rc"], - vintf_fragments: ["android.hardware.vibrator-service.cs40l26.xml"], + name: "android.hardware.vibrator-service.cs40l26-private-lynx", + defaults: ["VibratorHalCs40l26BinaryDefaultsPrivateLynx"], + init_rc: ["android.hardware.vibrator-service.cs40l26-private-lynx.rc"], + vintf_fragments: ["android.hardware.vibrator-service.cs40l26-private-lynx.xml"], srcs: ["service.cpp"], - shared_libs: ["android.hardware.vibrator-impl.cs40l26"], - proprietary: true, -} - -cc_binary { - name: "android.hardware.vibrator-service.cs40l26-dual", - defaults: ["VibratorHalCs40l26BinaryDefaults"], - init_rc: ["android.hardware.vibrator-service.cs40l26-dual.rc"], - vintf_fragments: ["android.hardware.vibrator-service.cs40l26-dual.xml"], - srcs: ["service.cpp"], - shared_libs: ["android.hardware.vibrator-impl.cs40l26"], - cflags: ["-DVIBRATOR_NAME=\"dual\""], + shared_libs: ["android.hardware.vibrator-impl.cs40l26-private-lynx"], proprietary: true, } diff --git a/vibrator/cs40l26/CapoDetector.cpp b/vibrator/cs40l26/CapoDetector.cpp new file mode 100644 index 0000000..1b8ba89 --- /dev/null +++ b/vibrator/cs40l26/CapoDetector.cpp @@ -0,0 +1,216 @@ +/* + * Copyright 2022 Google LLC. All Rights Reserved. + * + * 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 "CapoDetector.h" +#include +#include +#include + +#include + +#ifdef LOG_TAG +#undef LOG_TAG +#define LOG_TAG "CapoDetector" +#endif + +namespace android { +namespace chre { + +namespace { // anonymous namespace for file-local definitions + +static capo::ConfigureDetector_ConfigData config_data = capo::ConfigureDetector_ConfigData(); +static capo::ConfigureDetector msg = capo::ConfigureDetector(); + + +/** + * Called when onConnected() to send NanoappList request. + */ +void requestNanoappList(SocketClient &client) { + flatbuffers::FlatBufferBuilder builder; + HostProtocolHost::encodeNanoappListRequest(builder); + if (!client.sendMessage(builder.GetBufferPointer(), builder.GetSize())) { + ALOGE("Failed to send NanoappList request"); + } +} + +} // namespace + +/** + * Called when initializing connection with CHRE socket. + */ +sp CapoDetector::start() { + sp listener = new CapoDetector(); + if (!listener->connectInBackground(kChreSocketName, listener)) { + ALOGE("Couldn't connect to CHRE socket"); + return nullptr; + } + ALOGI("%s connect to CHRE socket.", __func__); + + return listener; +} + +/** + * Called when the socket is successfully (re-)connected. + * Reset the position and try to send NanoappList request. + */ +void CapoDetector::onConnected() { + flatbuffers::FlatBufferBuilder builder; + + // Reset the last position type. + last_position_type_ = capo::PositionType::UNKNOWN; + requestNanoappList(*this); +} + +/** + * Called when we have failed to (re-)connect the socket after many attempts + * and are giving up. + */ +void CapoDetector::onConnectionAborted() { + ALOGE("%s, Capo Aborting Connection!", __func__); +} + +/** + * Invoked when the socket is disconnected, and this connection loss was not + * the result of an explicit call to disconnect(). + * Reset the position while disconnecting. + */ + +void CapoDetector::onDisconnected() { + last_position_type_ = capo::PositionType::UNKNOWN; +} + +/** + * Decode unix socket msgs to CHRE messages, and call the appropriate + * callback depending on the CHRE message. + */ +void CapoDetector::onMessageReceived(const void *data, size_t length) { + if (!HostProtocolHost::decodeMessageFromChre(data, length, *this)) { + ALOGE("Failed to decode message"); + } +} + +/** + * Listen for messages from capo nanoapp and handle the message. + */ +void CapoDetector::handleNanoappMessage(const fbs::NanoappMessageT &message) { + ALOGI("%s, Id %" PRIu64 ", type %d, size %d", __func__, message.app_id, message.message_type, + static_cast(message.message.size())); + // Exclude the message with unmatched nanoapp id. + if (message.app_id != kCapoNanoappId) + return; + + // Handle the message with message_type. + switch (message.message_type) { + case capo::MessageType::ACK_NOTIFICATION: { + capo::AckNotification gd; + gd.set_notification_type(static_cast(message.message[1])); + ALOGD("%s, get notification event from capo nanoapp, type %d", __func__, + gd.notification_type()); + break; + } + case capo::MessageType::POSITION_DETECTED: { + capo::PositionDetected gd; + gd.set_position_type(static_cast(message.message[1])); + ALOGD("%s, get position event from capo nanoapp, type %d", __func__, + gd.position_type()); + + // Callback to function while getting carried position event. + if (callback_func_ != nullptr) { + last_position_type_ = gd.position_type(); + ALOGD("%s, sent position type %d to callback function", __func__, + last_position_type_); + callback_func_(last_position_type_); + } + break; + } + default: + ALOGE("%s, get invalid message, type: %" PRIu32 ", from capo nanoapp.", __func__, + message.message_type); + break; + } +} + +/** + * Handle the response of a NanoappList request. + * Ensure that capo nanoapp is running. + */ +void CapoDetector::handleNanoappListResponse(const fbs::NanoappListResponseT &response) { + for (const std::unique_ptr &nanoapp : response.nanoapps) { + if (nanoapp->app_id == kCapoNanoappId) { + if (nanoapp->enabled) + enable(); + else + ALOGE("Capo nanoapp not enabled"); + return; + } + } + ALOGE("Capo nanoapp not found"); +} + +/** + * Send enabling message to the nanoapp. + */ +void CapoDetector::enable() { + // Create CHRE message with serialized message + flatbuffers::FlatBufferBuilder builder, config_builder, force_builder; + + config_data.set_still_time_threshold_nanosecond(mCapoDetectorMDParameters.still_time_threshold_ns); + config_data.set_window_width_nanosecond(mCapoDetectorMDParameters.window_width_ns); + config_data.set_motion_confidence_threshold(mCapoDetectorMDParameters.motion_confidence_threshold); + config_data.set_still_confidence_threshold(mCapoDetectorMDParameters.still_confidence_threshold); + config_data.set_var_threshold(mCapoDetectorMDParameters.var_threshold); + config_data.set_var_threshold_delta(mCapoDetectorMDParameters.var_threshold_delta); + + msg.set_allocated_config_data(&config_data); + + auto pb_size = msg.ByteSizeLong(); + auto pb_data = std::make_unique(pb_size); + + if (!msg.SerializeToArray(pb_data.get(), pb_size)) { + ALOGE("Failed to serialize message."); + } + + ALOGI("Configuring CapoDetector"); + // Configure the detector from host-side + android::chre::HostProtocolHost::encodeNanoappMessage( + config_builder, getNanoppAppId(), capo::MessageType::CONFIGURE_DETECTOR, getHostEndPoint(), + pb_data.get(), pb_size); + ALOGI("Sending capo config message to Nanoapp, %" PRIu32 " bytes", config_builder.GetSize()); + if (!sendMessage(config_builder.GetBufferPointer(), config_builder.GetSize())) { + ALOGE("Failed to send config event for capo nanoapp"); + } + + ALOGI("Enabling CapoDetector"); + android::chre::HostProtocolHost::encodeNanoappMessage( + builder, getNanoppAppId(), capo::MessageType::ENABLE_DETECTOR, getHostEndPoint(), + /*messageData*/ nullptr, /*messageDataLenbuffer*/ 0); + ALOGI("Sending enable message to Nanoapp, %" PRIu32 " bytes", builder.GetSize()); + if (!sendMessage(builder.GetBufferPointer(), builder.GetSize())) { + ALOGE("Failed to send enable event for capo nanoapp"); + } + + ALOGI("Forcing CapoDetector to update state"); + // Force an updated state upon connection + android::chre::HostProtocolHost::encodeNanoappMessage( + force_builder, getNanoppAppId(), capo::MessageType::FORCE_UPDATE, getHostEndPoint(), + /*messageData*/ nullptr, /*messageDataLenbuffer*/ 0); + ALOGI("Sending force-update message to Nanoapp, %" PRIu32 " bytes", force_builder.GetSize()); + if (!sendMessage(force_builder.GetBufferPointer(), force_builder.GetSize())) { + ALOGE("Failed to send force-update event for capo nanoapp"); + } +} + +} // namespace chre +} // namespace android diff --git a/vibrator/cs40l26/Hardware.h b/vibrator/cs40l26/Hardware.h index ae052ba..be97594 100644 --- a/vibrator/cs40l26/Hardware.h +++ b/vibrator/cs40l26/Hardware.h @@ -92,6 +92,22 @@ class HwApi : public Vibrator::HwApi, private HwApiBase { 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); } + uint32_t getContextScale() override { + return utils::getProperty("persist.vendor.vibrator.hal.context.scale", 100); + } + bool getContextEnable() override { + return utils::getProperty("persist.vendor.vibrator.hal.context.enable", false); + } + uint32_t getContextSettlingTime() override { + return utils::getProperty("persist.vendor.vibrator.hal.context.settlingtime", 3000); + } + uint32_t getContextCooldownTime() override { + return utils::getProperty("persist.vendor.vibrator.hal.context.cooldowntime", 1000); + } + bool getContextFadeEnable() override { + return utils::getProperty("persist.vendor.vibrator.hal.context.fade", false); + } + // 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 = { diff --git a/vibrator/cs40l26/Vibrator.cpp b/vibrator/cs40l26/Vibrator.cpp index 3af057c..21a1682 100644 --- a/vibrator/cs40l26/Vibrator.cpp +++ b/vibrator/cs40l26/Vibrator.cpp @@ -28,11 +28,22 @@ #include #include #include +#include +#include + +#include "CapoDetector.h" #ifndef ARRAY_SIZE #define ARRAY_SIZE(x) (sizeof((x)) / sizeof((x)[0])) #endif +#ifdef LOG_TAG +#undef LOG_TAG +#define LOG_TAG "Vibrator" +#endif + +using CapoDetector = android::chre::CapoDetector; + namespace aidl { namespace android { namespace hardware { @@ -89,15 +100,34 @@ 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; +#ifndef DISABLE_ADAPTIVE_HAPTICS_FEATURE +static constexpr bool mAdaptiveHapticsEnable = true; +#else +static constexpr bool mAdaptiveHapticsEnable = false; +#endif /* DISABLE_ADAPTIVE_HAPTICS_FEATURE */ - if (maximum == 0 || ratio > 100) - ratio = 100; +static sp vibeContextListener; +uint8_t mCapoDeviceState = 0; +uint32_t mLastFaceUpEvent = 0; +uint32_t mLastEffectPlayedTime = 0; +float mLastPlayedScale = 0; - return std::round(ratio); +static uint32_t getCurrentTimeInMs(void) { + return std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count(); +} + +static void capoEventCallback(uint8_t eventId) { + ALOGD("Vibrator %s, From: 0x%x To: 0x%x", __func__, mCapoDeviceState, (uint32_t)eventId); + // Record the last moment we were in FACE_UP state + if (mCapoDeviceState == capo::PositionType::ON_TABLE_FACE_UP || + eventId == capo::PositionType::ON_TABLE_FACE_UP) { + mLastFaceUpEvent = getCurrentTimeInMs(); + } + mCapoDeviceState = eventId; +} + +static uint8_t getDeviceState(void) { + return mCapoDeviceState; } enum WaveformBankID : uint8_t { @@ -366,6 +396,18 @@ Vibrator::Vibrator(std::unique_ptr hwapi, std::unique_ptr hwcal) } mHwApi->setMinOnOffInterval(MIN_ON_OFF_INTERVAL_US); + + if (mAdaptiveHapticsEnable) { + vibeContextListener = CapoDetector::start(); + if (vibeContextListener == nullptr) { + ALOGE("%s, CapoDetector failed to start", __func__); + } else { + ALOGD("%s, CapoDetector started successfully! NanoAppID: 0x%x", __func__, + (uint32_t)vibeContextListener->getNanoppAppId()); + vibeContextListener->setCallback(capoEventCallback); + ALOGD("%s, CapoDetector Set Callback function from vibe", __func__); + } + } } ndk::ScopedAStatus Vibrator::getCapabilities(int32_t *_aidl_return) { @@ -666,8 +708,70 @@ ndk::ScopedAStatus Vibrator::on(uint32_t timeoutMs, uint32_t effectIndex, dspmem return ndk::ScopedAStatus::ok(); } -ndk::ScopedAStatus Vibrator::setEffectAmplitude(float amplitude, float maximum) { - uint16_t scale = amplitudeToScale(amplitude, maximum); +uint16_t Vibrator::amplitudeToScale(float amplitude, float maximum, bool scalable) { + float ratio = 100; /* Unit: % */ + + if (maximum != 0) + ratio = amplitude / maximum * 100; + + if (maximum == 0 || ratio > 100) + ratio = 100; + + if (scalable && mContextEnable & mAdaptiveHapticsEnable) { + uint32_t now = getCurrentTimeInMs(); + uint32_t last_played = mLastEffectPlayedTime; + float context_scale = 1.0; + bool device_face_up = getDeviceState() == capo::PositionType::ON_TABLE_FACE_UP; + float pre_scaled_ratio = ratio; + mLastEffectPlayedTime = now; + + ALOGD("Vibrator Now: %u, Last: %u, ScaleTime: %u, Since? %d", now, mLastFaceUpEvent, mScaleTime, (now < mLastFaceUpEvent + mScaleTime)); + /* If the device is face-up or within the fade scaling range, find new scaling factor */ + if (device_face_up || now < mLastFaceUpEvent + mScaleTime) { + /* Device is face-up, so we will scale it down. Start with highest scaling factor */ + context_scale = mScalingFactor <= 100 ? static_cast(mScalingFactor)/100 : 1.0; + if (mFadeEnable && mScaleTime > 0 && (context_scale < 1.0) && (now < mLastFaceUpEvent + mScaleTime) && !device_face_up) { + float fade_scale = static_cast(now - mLastFaceUpEvent)/static_cast(mScaleTime); + context_scale += ((1.0 - context_scale)*fade_scale); + ALOGD("Vibrator fade scale applied: %f", fade_scale); + } + ratio *= context_scale; + ALOGD("Vibrator adjusting for face-up: pre: %f, post: %f", + std::round(pre_scaled_ratio), std::round(ratio)); + } + + /* If we haven't played an effect within the cooldown time, save the scaling factor */ + if ((now - last_played) > mScaleCooldown) { + ALOGD("Vibrator updating lastplayed scale, old: %f, new: %f", mLastPlayedScale, context_scale); + mLastPlayedScale = context_scale; + } + else { + /* Override the scale to match previously played scale */ + ratio = mLastPlayedScale * pre_scaled_ratio; + ALOGD("Vibrator repeating last scale: %f, new ratio: %f, duration since last: %u", mLastPlayedScale, ratio, (now - last_played)); + } + } + + return std::round(ratio); +} + +void Vibrator::updateContext() { + mContextEnable = mHwApi->getContextEnable(); + mFadeEnable = mHwApi->getContextFadeEnable(); + mScalingFactor = mHwApi->getContextScale(); + mScaleTime = mHwApi->getContextSettlingTime(); + mScaleCooldown = mHwApi->getContextCooldownTime(); +} + +ndk::ScopedAStatus Vibrator::setEffectAmplitude(float amplitude, float maximum, bool scalable) { + uint16_t scale; + + if (mAdaptiveHapticsEnable && scalable) { + updateContext(); + } + + scale = amplitudeToScale(amplitude, maximum, scalable); + 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); @@ -680,7 +784,7 @@ ndk::ScopedAStatus Vibrator::setGlobalAmplitude(bool set) { if (!set) { mLongEffectScale = 1.0; // Reset the scale for the later new effect. } - return setEffectAmplitude(amplitude, VOLTAGE_SCALE_MAX); + return setEffectAmplitude(amplitude, VOLTAGE_SCALE_MAX, true); } ndk::ScopedAStatus Vibrator::getSupportedAlwaysOnEffects(std::vector * /*_aidl_return*/) { @@ -1054,6 +1158,16 @@ binder_status_t Vibrator::dump(int fd, const char **args, uint32_t numArgs) { mHwCal->debug(fd); + dprintf(fd, "Capo Info\n"); + if (vibeContextListener) { + dprintf(fd, "Capo ID: 0x%x\n", (uint32_t)(vibeContextListener->getNanoppAppId())); + dprintf(fd, "Capo State: %d DetectedState: %d\n", vibeContextListener->getCarriedPosition(), + getDeviceState()); + } else { + dprintf(fd, "Capo ID: 0x%x\n", (uint32_t)(0xdeadbeef)); + dprintf(fd, "Capo State: %d DetectedState: %d\n", (uint32_t)0x454545, getDeviceState()); + } + fsync(fd); return STATUS_OK; } @@ -1265,7 +1379,7 @@ exit: ndk::ScopedAStatus Vibrator::performEffect(uint32_t effectIndex, uint32_t volLevel, dspmem_chunk *ch, const std::shared_ptr &callback) { - setEffectAmplitude(volLevel, VOLTAGE_SCALE_MAX); + setEffectAmplitude(volLevel, VOLTAGE_SCALE_MAX, false); return on(MAX_TIME_MS, effectIndex, ch, callback); } diff --git a/vibrator/cs40l26/Vibrator.h b/vibrator/cs40l26/Vibrator.h index 220c974..7c9e99c 100644 --- a/vibrator/cs40l26/Vibrator.h +++ b/vibrator/cs40l26/Vibrator.h @@ -23,6 +23,8 @@ #include #include #include +#include +#include namespace aidl { namespace android { @@ -61,6 +63,21 @@ class Vibrator : public BnVibrator { virtual bool setRedcCompEnable(bool value) = 0; // Stores the minumun delay time between playback and stop effects. virtual bool setMinOnOffInterval(uint32_t value) = 0; + // Gets the scaling factor for contextual haptic events. + virtual uint32_t getContextScale() = 0; + // Gets the enable status for contextual haptic events. + virtual bool getContextEnable() = 0; + // Gets the settling time for contextual haptic events. + // This will allow the device to stay face up for the duration given, + // even if InMotion events were detected. + virtual uint32_t getContextSettlingTime() = 0; + // Gets the cooldown time for contextual haptic events. + // This is used to avoid changing the scale of close playback events. + virtual uint32_t getContextCooldownTime() = 0; + // Checks the enable status for contextual haptics fade feature. When enabled + // this feature will cause the scaling factor to fade back up to max over + // the setting time set, instead of instantaneously changing it back to max. + virtual bool getContextFadeEnable() = 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. @@ -158,7 +175,7 @@ class Vibrator : public BnVibrator { 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 setEffectAmplitude(float amplitude, float maximum, bool scalable); ndk::ScopedAStatus setGlobalAmplitude(bool set); // 'simple' effects are those precompiled and loaded into the controller ndk::ScopedAStatus getSimpleDetails(Effect effect, EffectStrength strength, @@ -181,6 +198,8 @@ class Vibrator : public BnVibrator { bool findHapticAlsaDevice(int *card, int *device); bool hasHapticAlsaDevice(); bool enableHapticPcmAmp(struct pcm **haptic_pcm, bool enable, int card, int device); + uint16_t amplitudeToScale(float amplitude, float maximum, bool scalable); + void updateContext(); std::unique_ptr mHwApi; std::unique_ptr mHwCal; @@ -200,6 +219,11 @@ class Vibrator : public BnVibrator { bool mIsUnderExternalControl; float mLongEffectScale = 1.0; bool mIsChirpEnabled; + uint32_t mScaleTime; + bool mFadeEnable; + uint32_t mScalingFactor; + uint32_t mScaleCooldown; + bool mContextEnable; uint32_t mSupportedPrimitivesBits = 0x0; std::vector mSupportedPrimitives; bool mConfigHapticAlsaDeviceDone{false}; diff --git a/vibrator/cs40l26/android.hardware.vibrator-service.cs40l26.rc b/vibrator/cs40l26/android.hardware.vibrator-service.cs40l26-private-lynx.rc similarity index 96% rename from vibrator/cs40l26/android.hardware.vibrator-service.cs40l26.rc rename to vibrator/cs40l26/android.hardware.vibrator-service.cs40l26-private-lynx.rc index 0fcca56..6986d1a 100644 --- a/vibrator/cs40l26/android.hardware.vibrator-service.cs40l26.rc +++ b/vibrator/cs40l26/android.hardware.vibrator-service.cs40l26-private-lynx.rc @@ -20,10 +20,10 @@ on property:vendor.all.modules.ready=1 enable vendor.vibrator.cs40l26 -service vendor.vibrator.cs40l26 /vendor/bin/hw/android.hardware.vibrator-service.cs40l26 +service vendor.vibrator.cs40l26 /vendor/bin/hw/android.hardware.vibrator-service.cs40l26-private-lynx class hal user system - group system input + group system input context_hub setenv INPUT_EVENT_NAME cs40l26_input setenv INPUT_EVENT_PATH /dev/input/event* diff --git a/vibrator/cs40l26/android.hardware.vibrator-service.cs40l26.xml b/vibrator/cs40l26/android.hardware.vibrator-service.cs40l26-private-lynx.xml similarity index 100% rename from vibrator/cs40l26/android.hardware.vibrator-service.cs40l26.xml rename to vibrator/cs40l26/android.hardware.vibrator-service.cs40l26-private-lynx.xml diff --git a/vibrator/cs40l26/device.mk b/vibrator/cs40l26/device.mk index 5a3dd4f..68860ef 100644 --- a/vibrator/cs40l26/device.mk +++ b/vibrator/cs40l26/device.mk @@ -1,5 +1,5 @@ PRODUCT_PACKAGES += \ - android.hardware.vibrator-service.cs40l26 + android.hardware.vibrator-service.cs40l26-private-lynx BOARD_SEPOLICY_DIRS += \ hardware/google/pixel-sepolicy/vibrator/common \ diff --git a/vibrator/cs40l26/inc/CapoDetector.h b/vibrator/cs40l26/inc/CapoDetector.h new file mode 100644 index 0000000..4b898ef --- /dev/null +++ b/vibrator/cs40l26/inc/CapoDetector.h @@ -0,0 +1,107 @@ +/* + * Copyright 2022 Google LLC. All Rights Reserved. + * + * 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 "proto/capo.pb.h" + +using android::sp; +using android::chre::HostProtocolHost; +using android::chre::IChreMessageHandlers; +using android::chre::SocketClient; + +// following convention of CHRE code. +namespace fbs = ::chre::fbs; + +namespace android { +namespace chre { + +#define NS_FROM_MS(x) ((x)*1000000) + +struct CapoMDParams { + uint64_t still_time_threshold_ns; + uint32_t window_width_ns; + float motion_confidence_threshold; + float still_confidence_threshold; + float var_threshold; + float var_threshold_delta; +}; + +class CapoDetector : public android::chre::SocketClient::ICallbacks, + public android::chre::IChreMessageHandlers, + public android::chre::SocketClient { + public: + // Typedef declaration for callback function. + typedef std::function cb_fn_t; + + // Called when initializing connection with CHRE socket. + static android::sp start(); + // Called when the socket is successfully (re-)connected. + // Reset the position and try to send NanoappList request. + void onConnected() override; + // Called when we have failed to (re-)connect the socket after many attempts + // and are giving up. + void onConnectionAborted() override; + // Invoked when the socket is disconnected, and this connection loss + // was not the result of an explicit call to disconnect(). + // Reset the position while disconnecting. + void onDisconnected() override; + // Decode unix socket msgs to CHRE messages, and call the appropriate + // callback depending on the CHRE message. + void onMessageReceived(const void *data, size_t length) override; + // Listen for messages from capo nanoapp and handle the message. + void handleNanoappMessage(const ::chre::fbs::NanoappMessageT &message) override; + // Handle the response of a NanoappList request. + // Ensure that capo nanoapp is running. + void handleNanoappListResponse(const ::chre::fbs::NanoappListResponseT &response) override; + // Send enabling message to the nanoapp. + void enable(); + + // Get last carried position type. + uint8_t getCarriedPosition() { return last_position_type_; } + // Get the host endpoint. + uint16_t getHostEndPoint() { return kHostEndpoint; } + // Get the capo nanoapp ID. + uint64_t getNanoppAppId() { return kCapoNanoappId; } + // Set up callback_func_ if needed. + void setCallback(cb_fn_t cb) { callback_func_ = cb; } + + private: + // Nanoapp ID of capo, ref: go/nanoapp-id-tracker. + static constexpr uint64_t kCapoNanoappId = 0x476f6f676c001020ULL; + // String of socket name for connecting chre. + static constexpr char kChreSocketName[] = "chre"; + // The host endpoint we use when sending message. + // Set with 0x9020 based on 0x8000 AND capo_app_id(1020). + // Ref: go/host-endpoint-id-tracker. + static constexpr uint16_t kHostEndpoint = 0x9020; + // Using for hal layer callback function. + cb_fn_t callback_func_ = nullptr; + // Last carried position received from the nano app + capo::PositionType last_position_type_ = capo::PositionType::UNKNOWN; + // Motion detector parameters for host-driven capo config + const struct CapoMDParams mCapoDetectorMDParameters { + .still_time_threshold_ns = NS_FROM_MS(500), + .window_width_ns = NS_FROM_MS(100), + .motion_confidence_threshold = 0.98f, + .still_confidence_threshold = 0.99f, + .var_threshold = 0.0125f, + .var_threshold_delta = 0.0125f, + }; +}; + +} // namespace chre +} // namespace android diff --git a/vibrator/cs40l26/proto/capo.proto b/vibrator/cs40l26/proto/capo.proto new file mode 100644 index 0000000..00b3186 --- /dev/null +++ b/vibrator/cs40l26/proto/capo.proto @@ -0,0 +1,148 @@ +syntax = "proto3"; + +package capo; + +// The message types used in capo nanoapp. Some of them are H2C +// (Host-To-CHRE) and others are C2H (CHRE-To-Host). One message type must be +// either H2C or C2H. Each message type can choose to have payload or not. +enum MessageType { + // Explicitly prevents 0 from being used as a valid message type. + // Doing so protects from obscure bugs caused by default-initialized values. + INVALID = 0; + + // Detector configuration related message start from 100. + // Signal for host to acknowledge the notification. + // It contains AckNotification payload. + ACK_NOTIFICATION = 100; + + // Signal to enable the carried position detector for device. No payload. + ENABLE_DETECTOR = 101; + + // Signal to disable the carried position detector for device. No payload. + DISABLE_DETECTOR = 102; + + // Signal to request most recent carried position detector state. No payload. + REQUEST_UPDATE = 103; + + // Signal to force carried position detector to refresh state. No payload. + FORCE_UPDATE = 104; + + // Configure the detector with desired parameters. ConfigureDetector payload. + CONFIGURE_DETECTOR = 105; + + // Position Detection related message start from 200. + // Signal while carried position of device detected. + // It contains PositionDetected payload. + POSITION_DETECTED = 200; +} + +// Notification Type. +enum NotificationType { + // Explicitly prevents 0 from being used as a valid notification type. + // Doing so protects from obscure bugs caused by default-initialized values. + INVALID_NOTIFICATION = 0; + + // Notification of enabling the carried position detector for device. + ENABLE_NOTIFICATION = 1; + + // Notification of disabling the carried position detector for device. + DISABLE_NOTIFICATION = 2; + + // Notification of request update from the carried position detector. + REQUEST_UPDATE_NOTIFICATION = 3; + + // Notification of force update from the carried position detector. + FORCE_UPDATE_NOTIFICATION = 4; + + // Notification of configure message. + CONFIGURE_NOTIFICATION = 5; +} + +// This message type used for host to acknowledge the notification. +message AckNotification { + // Sent a notification type for host to acknowledge. + NotificationType notification_type = 1; +} + +// Position type. +enum PositionType { + // Explicitly prevents 0 from being used as a valid carried position type. + // Doing so protects from obscure bugs caused by default-initialized values. + UNKNOWN = 0; + + // Carried position while device is in motion. + IN_MOTION = 1; + + // Carried position while device is on table and faces up. + ON_TABLE_FACE_UP = 2; + + // Carried position while device is on table and faces down. + ON_TABLE_FACE_DOWN = 3; + + // Carried position while device is stationary in unknown orientation. + STATIONARY_UNKNOWN = 4; +} + +// This message type used to notify host a position was a detected. +message PositionDetected { + // Sent a position type that is defined in PositionTypes. + PositionType position_type = 1; +} + +// Predefined configurations for detector. +enum ConfigPresetType { + // Explicitly prevents 0 from being used as a valid type. + // Doing so protects from obscure bugs caused by default-initialized values. + CONFIG_PRESET_UNSPECIFIED = 0; + + // Default preset. + CONFIG_PRESET_DEFAULT = 1; + + // Preset for sticky-stationary behavior. + CONFIG_PRESET_STICKY_STATIONARY = 2; +} + +message ConfigureDetector { + // Ref: cs/location/lbs/contexthub/nanoapps/motiondetector/motion_detector.h + message ConfigData { + // These algo parameters are exposed to enable tuning via server flags. + // The amount of time that the algorithm's computed stillness confidence + // must exceed still_confidence_threshold before entering the stationary + // state. Increasing this value will make the algorithm take longer to + // transition from the in motion state to the stationary state. + uint64 still_time_threshold_nanosecond = 1; + + // The amount of time in which the variance should be averaged. Increasing + // this value will effectively smooth the input data, making the algorithm + // less likely to transition between states. + uint32 window_width_nanosecond = 2; + + // The required confidence that the device is in motion before entering the + // motion state. Valid range is [0.0, 1.0], where 1.0 indicates that the + // algorithm must be 100% certain that the device is moving before entering + // the motion state. If the Instant Motion sensor is triggered, this value + // is ignored and the algorithm is immediately transitioned into the in + // motion state. + float motion_confidence_threshold = 3; + + // The required confidence that the device is stationary before entering the + // stationary state. Valid range is [0.0, 1.0], where 1.0 indicates that the + // algorithm must be 100% certain that the device is stationary before + // entering the stationary state. + float still_confidence_threshold = 4; + + // The variance threshold for the StillnessDetector algorithm. Increasing + // this value causes the algorithm to be less likely to detect motion. + float var_threshold = 5; + + // The variance threshold delta for the StillnessDetector algorithm about + // which the stationary confidence is calculated. Valid range is + // [0.0, var_threshold]. + float var_threshold_delta = 6; + } + + oneof type { + ConfigPresetType preset_type = 1; + ConfigData config_data = 2; + } +} diff --git a/vibrator/cs40l26/tests/Android.bp b/vibrator/cs40l26/tests/Android.bp index 93c9a9f..c53ec23 100644 --- a/vibrator/cs40l26/tests/Android.bp +++ b/vibrator/cs40l26/tests/Android.bp @@ -18,8 +18,8 @@ package { } cc_test { - name: "VibratorHalCs40l26TestSuite", - defaults: ["VibratorHalCs40l26TestDefaults"], + name: "VibratorHalCs40l26TestSuitePrivateLynx", + defaults: ["VibratorHalCs40l26TestDefaultsPrivateLynx"], srcs: [ "test-hwcal.cpp", "test-hwapi.cpp", diff --git a/vibrator/cs40l26/tests/mocks.h b/vibrator/cs40l26/tests/mocks.h index 21466a0..5ba0270 100644 --- a/vibrator/cs40l26/tests/mocks.h +++ b/vibrator/cs40l26/tests/mocks.h @@ -34,6 +34,11 @@ class MockApi : public ::aidl::android::hardware::vibrator::Vibrator::HwApi { MOCK_METHOD1(setF0CompEnable, bool(bool value)); MOCK_METHOD1(setRedcCompEnable, bool(bool value)); MOCK_METHOD1(setMinOnOffInterval, bool(uint32_t value)); + MOCK_METHOD0(getContextScale, uint32_t()); + MOCK_METHOD0(getContextEnable, bool()); + MOCK_METHOD0(getContextSettlingTime, uint32_t()); + MOCK_METHOD0(getContextCooldownTime, uint32_t()); + MOCK_METHOD0(getContextFadeEnable, bool()); 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)); diff --git a/vibrator/cs40l26/tests/test-vibrator.cpp b/vibrator/cs40l26/tests/test-vibrator.cpp index a8bedd5..603ef5e 100644 --- a/vibrator/cs40l26/tests/test-vibrator.cpp +++ b/vibrator/cs40l26/tests/test-vibrator.cpp @@ -284,6 +284,11 @@ class VibratorTest : public Test { EXPECT_CALL(*mMockApi, setFFEffect(_, _, _)).Times(times); EXPECT_CALL(*mMockApi, setFFPlay(_, _, _)).Times(times); EXPECT_CALL(*mMockApi, setMinOnOffInterval(_)).Times(times); + EXPECT_CALL(*mMockApi, getContextScale()).Times(times); + EXPECT_CALL(*mMockApi, getContextEnable()).Times(times); + EXPECT_CALL(*mMockApi, getContextSettlingTime()).Times(times); + EXPECT_CALL(*mMockApi, getContextCooldownTime()).Times(times); + EXPECT_CALL(*mMockApi, getContextFadeEnable()).Times(times); EXPECT_CALL(*mMockApi, getHapticAlsaDevice(_, _)).Times(times); EXPECT_CALL(*mMockApi, setHapticPcmAmp(_, _, _, _)).Times(times); @@ -363,6 +368,11 @@ TEST_F(VibratorTest, Constructor) { .WillOnce(DoAll(SetArgPointee<0>(supportedPrimitivesBits), Return(true))); EXPECT_CALL(*mMockApi, setMinOnOffInterval(MIN_ON_OFF_INTERVAL_US)).WillOnce(Return(true)); + EXPECT_CALL(*mMockApi, getContextScale()).WillOnce(Return(0)); + EXPECT_CALL(*mMockApi, getContextEnable()).WillOnce(Return(false)); + EXPECT_CALL(*mMockApi, getContextSettlingTime()).WillOnce(Return(0)); + EXPECT_CALL(*mMockApi, getContextCooldownTime()).WillOnce(Return(0)); + EXPECT_CALL(*mMockApi, getContextFadeEnable()).WillOnce(Return(false)); createVibrator(std::move(mockapi), std::move(mockcal), false); }