Преглед изворни кода

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 <[email protected]>
Chris Paulo пре 2 година
родитељ
комит
0db068b63c

+ 7 - 0
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

+ 7 - 2
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

+ 6 - 6
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",
     ],


+ 1 - 1
vibrator/common/Android.bp

@@ -18,7 +18,7 @@ package {
 }
 
 cc_library {
-    name: "PixelVibratorCommon",
+    name: "PixelVibratorCommonPrivateLynx",
     srcs: [
         "HardwareBase.cpp",
     ],

+ 50 - 29
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,
-    visibility: [":__subpackages__"],
+    owner: "google",
+    srcs: [
+        "proto/capo.proto",
+    ],
+    shared_libs: [
+        "libprotobuf-cpp-full",
+    ],
+    export_include_dirs: [
+        "inc",
+    ],
+    proto: {
+        type: "lite",
+        export_proto_headers: true,
+    },
 }
 
-cc_binary {
-    name: "android.hardware.vibrator-service.cs40l26",
-    defaults: ["VibratorHalCs40l26BinaryDefaults"],
-    init_rc: ["android.hardware.vibrator-service.cs40l26.rc"],
-    vintf_fragments: ["android.hardware.vibrator-service.cs40l26.xml"],
-    srcs: ["service.cpp"],
-    shared_libs: ["android.hardware.vibrator-impl.cs40l26"],
-    proprietary: true,
+cc_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-dual",
-    defaults: ["VibratorHalCs40l26BinaryDefaults"],
-    init_rc: ["android.hardware.vibrator-service.cs40l26-dual.rc"],
-    vintf_fragments: ["android.hardware.vibrator-service.cs40l26-dual.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"],
-    cflags: ["-DVIBRATOR_NAME=\"dual\""],
+    shared_libs: ["android.hardware.vibrator-impl.cs40l26-private-lynx"],
     proprietary: true,
 }

+ 216 - 0
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 <google/protobuf/message.h>
+#include <google/protobuf/io/coded_stream.h>
+#include <google/protobuf/io/zero_copy_stream_impl.h>
+
+#include <log/log.h>
+
+#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> CapoDetector::start() {
+    sp<CapoDetector> 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<int>(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<capo::NotificationType>(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<capo::PositionType>(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<fbs::NanoappListEntryT> &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<uint8_t[]>(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

+ 16 - 0
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 = {

+ 125 - 11
vibrator/cs40l26/Vibrator.cpp

@@ -28,11 +28,22 @@
 #include <fstream>
 #include <iostream>
 #include <sstream>
+#include <ctime>
+#include <chrono>
+
+#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<CapoDetector> 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::milliseconds>(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> hwapi, std::unique_ptr<HwCal> 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<float>(mScalingFactor)/100 : 1.0;
+            if (mFadeEnable && mScaleTime > 0 && (context_scale < 1.0) && (now < mLastFaceUpEvent + mScaleTime) && !device_face_up) {
+                float fade_scale = static_cast<float>(now - mLastFaceUpEvent)/static_cast<float>(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<Effect> * /*_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<IVibratorCallback> &callback) {
-    setEffectAmplitude(volLevel, VOLTAGE_SCALE_MAX);
+    setEffectAmplitude(volLevel, VOLTAGE_SCALE_MAX, false);
 
     return on(MAX_TIME_MS, effectIndex, ch, callback);
 }

+ 25 - 1
vibrator/cs40l26/Vibrator.h

@@ -23,6 +23,8 @@
 #include <array>
 #include <fstream>
 #include <future>
+#include <ctime>
+#include <chrono>
 
 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<IVibratorCallback> &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<HwApi> mHwApi;
     std::unique_ptr<HwCal> 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<CompositePrimitive> mSupportedPrimitives;
     bool mConfigHapticAlsaDeviceDone{false};

+ 2 - 2
vibrator/cs40l26/android.hardware.vibrator-service.cs40l26.rc → 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*

+ 0 - 0
vibrator/cs40l26/android.hardware.vibrator-service.cs40l26.xml → vibrator/cs40l26/android.hardware.vibrator-service.cs40l26-private-lynx.xml


+ 1 - 1
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 \

+ 107 - 0
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 <chre_host/host_protocol_host.h>
+#include <chre_host/socket_client.h>
+
+#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<void(uint8_t)> cb_fn_t;
+
+    // Called when initializing connection with CHRE socket.
+    static android::sp<CapoDetector> 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

+ 148 - 0
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;
+  }
+}

+ 2 - 2
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",

+ 5 - 0
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));

+ 10 - 0
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);
 }