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 <chrispaulo@google.com>
This commit is contained in:
Chris Paulo 2022-11-02 22:21:50 +00:00 committed by TreeHugger Robot
parent 24789fe332
commit 0db068b63c
18 changed files with 730 additions and 56 deletions

View File

@ -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

View File

@ -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

View File

@ -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",
],

View File

@ -1,3 +1,4 @@
chasewu@google.com
michaelwr@google.com
taikuo@google.com
chrispaulo@google.com

View File

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

View File

@ -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,
}

View File

@ -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

View File

@ -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 = {

View File

@ -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);
}

View File

@ -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};

View File

@ -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*

View File

@ -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 \

View File

@ -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

View File

@ -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;
}
}

View File

@ -18,8 +18,8 @@ package {
}
cc_test {
name: "VibratorHalCs40l26TestSuite",
defaults: ["VibratorHalCs40l26TestDefaults"],
name: "VibratorHalCs40l26TestSuitePrivateLynx",
defaults: ["VibratorHalCs40l26TestDefaultsPrivateLynx"],
srcs: [
"test-hwcal.cpp",
"test-hwapi.cpp",

View File

@ -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));

View File

@ -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);
}