Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 1d88585f authored by Lais Andrade's avatar Lais Andrade Committed by Shunkai Yao
Browse files

Fix haptics scaling to match VibrationEffect function

Fix the scale function in ExternalVibrationUtils to match the one used
by VibrationEffect. Add flagged fix with new tests for the module
libvibrator.

Bug: 356144312
Flag: android.os.vibrator.fix_audio_coupled_haptics_scaling
Test: libvibrator_test
Change-Id: I56116536d7cddab29448f0e436ee752c7181eb45
Merged-In: I56116536d7cddab29448f0e436ee752c7181eb45
parent 351a8df9
Loading
Loading
Loading
Loading
+13 −0
Original line number Diff line number Diff line
@@ -24,6 +24,10 @@ package {
cc_defaults {
    name: "libvibrator_defaults",

    defaults: [
        "aconfig_lib_cc_shared_link.defaults",
    ],

    cflags: [
        "-Wall",
        "-Werror",
@@ -50,9 +54,11 @@ cc_library {
        "libbinder",
        "liblog",
        "libutils",
        "server_configurable_flags",
    ],

    whole_static_libs: [
        "android.os.vibrator.flags-aconfig-cc",
        "libvibratorutils",
    ],

@@ -79,8 +85,14 @@ cc_library {
    vendor_available: true,
    double_loadable: true,

    static_libs: [
        "android.os.vibrator.flags-aconfig-cc",
    ],

    shared_libs: [
        "liblog",
        "libutils",
        "server_configurable_flags",
    ],

    srcs: [
@@ -89,6 +101,7 @@ cc_library {

    visibility: [
        "//frameworks/native/libs/vibrator",
        "//frameworks/native/libs/vibrator/tests",
        "//frameworks/av/media/libeffects/hapticgenerator",
    ],
}
+60 −7
Original line number Diff line number Diff line
@@ -15,6 +15,9 @@
 */
#include <cstring>

#include <android_os_vibrator.h>

#include <algorithm>
#include <math.h>

#include <vibrator/ExternalVibrationUtils.h>
@@ -25,8 +28,9 @@ namespace {
static constexpr float HAPTIC_SCALE_VERY_LOW_RATIO = 2.0f / 3.0f;
static constexpr float HAPTIC_SCALE_LOW_RATIO = 3.0f / 4.0f;
static constexpr float HAPTIC_MAX_AMPLITUDE_FLOAT = 1.0f;
static constexpr float SCALE_GAMMA = 0.65f; // Same as VibrationEffect.SCALE_GAMMA

float getHapticScaleGamma(HapticLevel level) {
float getOldHapticScaleGamma(HapticLevel level) {
    switch (level) {
    case HapticLevel::VERY_LOW:
        return 2.0f;
@@ -41,7 +45,7 @@ float getHapticScaleGamma(HapticLevel level) {
    }
}

float getHapticMaxAmplitudeRatio(HapticLevel level) {
float getOldHapticMaxAmplitudeRatio(HapticLevel level) {
    switch (level) {
    case HapticLevel::VERY_LOW:
        return HAPTIC_SCALE_VERY_LOW_RATIO;
@@ -56,6 +60,52 @@ float getHapticMaxAmplitudeRatio(HapticLevel level) {
    }
}

/* Same as VibrationScaler.SCALE_LEVEL_* */
float getHapticScaleFactor(HapticLevel level) {
    switch (level) {
        case HapticLevel::VERY_LOW:
            return 0.6f;
        case HapticLevel::LOW:
            return 0.8f;
        case HapticLevel::HIGH:
            return 1.2f;
        case HapticLevel::VERY_HIGH:
            return 1.4f;
        default:
            return 1.0f;
    }
}

float applyOldHapticScale(float value, float gamma, float maxAmplitudeRatio) {
    float sign = value >= 0 ? 1.0 : -1.0;
    return powf(fabsf(value / HAPTIC_MAX_AMPLITUDE_FLOAT), gamma)
                * maxAmplitudeRatio * HAPTIC_MAX_AMPLITUDE_FLOAT * sign;
}

float applyNewHapticScale(float value, float scaleFactor) {
    float scale = powf(scaleFactor, 1.0f / SCALE_GAMMA);
    if (scaleFactor <= 1) {
        // Scale down is simply a gamma corrected application of scaleFactor to the intensity.
        // Scale up requires a different curve to ensure the intensity will not become > 1.
        return value * scale;
    }

    float sign = value >= 0 ? 1.0f : -1.0f;
    float extraScale = powf(scaleFactor, 4.0f - scaleFactor);
    float x = fabsf(value) * scale * extraScale;
    float maxX = scale * extraScale; // scaled x for intensity == 1

    float expX = expf(x);
    float expMaxX = expf(maxX);

    // Using f = tanh as the scale up function so the max value will converge.
    // a = 1/f(maxX), used to scale f so that a*f(maxX) = 1 (the value will converge to 1).
    float a = (expMaxX + 1.0f) / (expMaxX - 1.0f);
    float fx = (expX - 1.0f) / (expX + 1.0f);

    return sign * std::clamp(a * fx, 0.0f, 1.0f);
}

void applyHapticScale(float* buffer, size_t length, HapticScale scale) {
    if (scale.isScaleMute()) {
        memset(buffer, 0, length * sizeof(float));
@@ -65,15 +115,18 @@ void applyHapticScale(float* buffer, size_t length, HapticScale scale) {
        return;
    }
    HapticLevel hapticLevel = scale.getLevel();
    float scaleFactor = getHapticScaleFactor(hapticLevel);
    float adaptiveScaleFactor = scale.getAdaptiveScaleFactor();
    float gamma = getHapticScaleGamma(hapticLevel);
    float maxAmplitudeRatio = getHapticMaxAmplitudeRatio(hapticLevel);
    float oldGamma = getOldHapticScaleGamma(hapticLevel);
    float oldMaxAmplitudeRatio = getOldHapticMaxAmplitudeRatio(hapticLevel);

    for (size_t i = 0; i < length; i++) {
        if (hapticLevel != HapticLevel::NONE) {
            float sign = buffer[i] >= 0 ? 1.0 : -1.0;
            buffer[i] = powf(fabsf(buffer[i] / HAPTIC_MAX_AMPLITUDE_FLOAT), gamma)
                        * maxAmplitudeRatio * HAPTIC_MAX_AMPLITUDE_FLOAT * sign;
            if (android_os_vibrator_fix_audio_coupled_haptics_scaling()) {
                buffer[i] = applyNewHapticScale(buffer[i], scaleFactor);
            } else {
                buffer[i] = applyOldHapticScale(buffer[i], oldGamma, oldMaxAmplitudeRatio);
            }
        }

        if (adaptiveScaleFactor != 1.0f) {
+7 −0
Original line number Diff line number Diff line
{
  "postsubmit": [
    {
      "name": "libvibrator_test"
    }
  ]
}
+53 −0
Original line number Diff line number Diff line
// Copyright (C) 2024 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package {
    default_team: "trendy_team_haptics_framework",
    // See: http://go/android-license-faq
    // A large-scale-change added 'default_applicable_licenses' to import
    // all of the 'license_kinds' from "frameworks_native_license"
    // to get the below license kinds:
    //   SPDX-license-identifier-Apache-2.0
    default_applicable_licenses: ["frameworks_native_license"],
}

cc_test {
    name: "libvibrator_test",
    test_suites: ["general-tests"],
    defaults: [
        "aconfig_lib_cc_shared_link.defaults",
    ],
    srcs: [
        "ExternalVibrationTest.cpp",
        "ExternalVibrationUtilsTest.cpp",
    ],
    cflags: [
        "-Wall",
        "-Werror",
        "-Wextra",
    ],
    static_libs: [
        "android.os.vibrator.flags-aconfig-cc",
        "libflagtest",
        "libgtest",
        "liblog",
        "libvibrator",
        "libvibratorutils",
    ],
    shared_libs: [
        "libbase",
        "libbinder",
        "libutils",
        "server_configurable_flags",
    ],
}
+109 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2024 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *            http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include <binder/Parcel.h>
#include <gtest/gtest.h>
#include <vibrator/ExternalVibration.h>

using namespace android;
using namespace testing;

using HapticLevel = os::HapticLevel;
using ScaleLevel = os::ExternalVibrationScale::ScaleLevel;

class TestVibrationController : public os::IExternalVibrationController {
public:
    explicit TestVibrationController() {}
    IBinder *onAsBinder() override { return nullptr; }
    binder::Status mute(/*out*/ bool *ret) override {
        *ret = false;
        return binder::Status::ok();
    };
    binder::Status unmute(/*out*/ bool *ret) override {
        *ret = false;
        return binder::Status::ok();
    };
};

class ExternalVibrationTest : public Test {
protected:
    HapticLevel toHapticLevel(ScaleLevel level) {
        os::ExternalVibrationScale externalVibrationScale;
        externalVibrationScale.scaleLevel = level;
        os::HapticScale hapticScale =
                os::ExternalVibration::externalVibrationScaleToHapticScale(externalVibrationScale);
        return hapticScale.getLevel();
    }
};

TEST_F(ExternalVibrationTest, TestReadAndWriteToParcel) {
    int32_t uid = 1;
    std::string pkg("package.name");
    audio_attributes_t originalAttrs;
    originalAttrs.content_type = AUDIO_CONTENT_TYPE_SONIFICATION;
    originalAttrs.usage = AUDIO_USAGE_ASSISTANCE_SONIFICATION;
    originalAttrs.source = AUDIO_SOURCE_VOICE_COMMUNICATION;
    originalAttrs.flags = AUDIO_FLAG_BYPASS_MUTE;
    sp<TestVibrationController> vibrationController = new TestVibrationController();
    ASSERT_NE(vibrationController, nullptr);
    sp<os::ExternalVibration> original =
            new os::ExternalVibration(uid, pkg, originalAttrs, vibrationController);
    ASSERT_NE(original, nullptr);
    EXPECT_EQ(original->getUid(), uid);
    EXPECT_EQ(original->getPackage(), pkg);
    EXPECT_EQ(original->getAudioAttributes().content_type, originalAttrs.content_type);
    EXPECT_EQ(original->getAudioAttributes().usage, originalAttrs.usage);
    EXPECT_EQ(original->getAudioAttributes().source, originalAttrs.source);
    EXPECT_EQ(original->getAudioAttributes().flags, originalAttrs.flags);
    EXPECT_EQ(original->getController(), vibrationController);
    audio_attributes_t defaultAttrs;
    defaultAttrs.content_type = AUDIO_CONTENT_TYPE_UNKNOWN;
    defaultAttrs.usage = AUDIO_USAGE_UNKNOWN;
    defaultAttrs.source = AUDIO_SOURCE_DEFAULT;
    defaultAttrs.flags = AUDIO_FLAG_NONE;
    sp<os::ExternalVibration> parceled =
            new os::ExternalVibration(0, std::string(""), defaultAttrs, nullptr);
    ASSERT_NE(parceled, nullptr);
    Parcel parcel;
    original->writeToParcel(&parcel);
    parcel.setDataPosition(0);
    parceled->readFromParcel(&parcel);
    EXPECT_EQ(parceled->getUid(), uid);
    EXPECT_EQ(parceled->getPackage(), pkg);
    EXPECT_EQ(parceled->getAudioAttributes().content_type, originalAttrs.content_type);
    EXPECT_EQ(parceled->getAudioAttributes().usage, originalAttrs.usage);
    EXPECT_EQ(parceled->getAudioAttributes().source, originalAttrs.source);
    EXPECT_EQ(parceled->getAudioAttributes().flags, originalAttrs.flags);
    // TestVibrationController does not implement onAsBinder, skip controller parcel in this test.
}

TEST_F(ExternalVibrationTest, TestExternalVibrationScaleToHapticScale) {
    os::ExternalVibrationScale externalVibrationScale;
    externalVibrationScale.scaleLevel = ScaleLevel::SCALE_HIGH;
    externalVibrationScale.adaptiveHapticsScale = 0.8f;
    os::HapticScale hapticScale =
            os::ExternalVibration::externalVibrationScaleToHapticScale(externalVibrationScale);
    // Check scale factor is forwarded.
    EXPECT_EQ(hapticScale.getLevel(), HapticLevel::HIGH);
    EXPECT_EQ(hapticScale.getAdaptiveScaleFactor(), 0.8f);
    // Check conversion for all levels.
    EXPECT_EQ(toHapticLevel(ScaleLevel::SCALE_MUTE), HapticLevel::MUTE);
    EXPECT_EQ(toHapticLevel(ScaleLevel::SCALE_VERY_LOW), HapticLevel::VERY_LOW);
    EXPECT_EQ(toHapticLevel(ScaleLevel::SCALE_LOW), HapticLevel::LOW);
    EXPECT_EQ(toHapticLevel(ScaleLevel::SCALE_NONE), HapticLevel::NONE);
    EXPECT_EQ(toHapticLevel(ScaleLevel::SCALE_HIGH), HapticLevel::HIGH);
    EXPECT_EQ(toHapticLevel(ScaleLevel::SCALE_VERY_HIGH), HapticLevel::VERY_HIGH);
}
Loading