Merge "Update HAL with new PWLE V2 APIs" into main

This commit is contained in:
Ahmad Khalil
2024-08-22 00:37:24 +00:00
committed by Android (Google) Code Review
10 changed files with 783 additions and 6 deletions

View File

@@ -59,6 +59,11 @@ interface IVibrator {
android.hardware.vibrator.Braking[] getSupportedBraking();
void composePwle(in android.hardware.vibrator.PrimitivePwle[] composite, in android.hardware.vibrator.IVibratorCallback callback);
void performVendorEffect(in android.hardware.vibrator.VendorEffect vendorEffect, in android.hardware.vibrator.IVibratorCallback callback);
List<android.hardware.vibrator.PwleV2OutputMapEntry> getPwleV2FrequencyToOutputAccelerationMap();
int getPwleV2PrimitiveDurationMaxMillis();
int getPwleV2CompositionSizeMax();
int getPwleV2PrimitiveDurationMinMillis();
void composePwleV2(in android.hardware.vibrator.PwleV2Primitive[] composite, in android.hardware.vibrator.IVibratorCallback callback);
const int CAP_ON_CALLBACK = (1 << 0) /* 1 */;
const int CAP_PERFORM_CALLBACK = (1 << 1) /* 2 */;
const int CAP_AMPLITUDE_CONTROL = (1 << 2) /* 4 */;
@@ -71,4 +76,5 @@ interface IVibrator {
const int CAP_FREQUENCY_CONTROL = (1 << 9) /* 512 */;
const int CAP_COMPOSE_PWLE_EFFECTS = (1 << 10) /* 1024 */;
const int CAP_PERFORM_VENDOR_EFFECTS = (1 << 11) /* 2048 */;
const int CAP_COMPOSE_PWLE_EFFECTS_V2 = (1 << 12) /* 4096 */;
}

View File

@@ -0,0 +1,39 @@
/*
* 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.
*/
///////////////////////////////////////////////////////////////////////////////
// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. //
///////////////////////////////////////////////////////////////////////////////
// This file is a snapshot of an AIDL file. Do not edit it manually. There are
// two cases:
// 1). this is a frozen version file - do not edit this in any case.
// 2). this is a 'current' file. If you make a backwards compatible change to
// the interface (from the latest frozen version), the build system will
// prompt you to update this file with `m <name>-update-api`.
//
// You must not make a backward incompatible change to any AIDL file built
// with the aidl_interface module type with versions property set. The module
// type is used to build AIDL files in a way that they can be used across
// independently updatable components of the system. If a device is shipped
// with such a backward incompatible change, it has a high risk of breaking
// later when a module using the interface is updated, e.g., Mainline modules.
package android.hardware.vibrator;
@VintfStability
parcelable PwleV2OutputMapEntry {
float frequencyHz;
float maxOutputAccelerationGs;
}

View File

@@ -0,0 +1,40 @@
/*
* 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.
*/
///////////////////////////////////////////////////////////////////////////////
// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. //
///////////////////////////////////////////////////////////////////////////////
// This file is a snapshot of an AIDL file. Do not edit it manually. There are
// two cases:
// 1). this is a frozen version file - do not edit this in any case.
// 2). this is a 'current' file. If you make a backwards compatible change to
// the interface (from the latest frozen version), the build system will
// prompt you to update this file with `m <name>-update-api`.
//
// You must not make a backward incompatible change to any AIDL file built
// with the aidl_interface module type with versions property set. The module
// type is used to build AIDL files in a way that they can be used across
// independently updatable components of the system. If a device is shipped
// with such a backward incompatible change, it has a high risk of breaking
// later when a module using the interface is updated, e.g., Mainline modules.
package android.hardware.vibrator;
@VintfStability
parcelable PwleV2Primitive {
float amplitude;
float frequencyHz;
int timeMillis;
}

View File

@@ -23,6 +23,8 @@ import android.hardware.vibrator.Effect;
import android.hardware.vibrator.EffectStrength;
import android.hardware.vibrator.IVibratorCallback;
import android.hardware.vibrator.PrimitivePwle;
import android.hardware.vibrator.PwleV2OutputMapEntry;
import android.hardware.vibrator.PwleV2Primitive;
import android.hardware.vibrator.VendorEffect;
@VintfStability
@@ -75,6 +77,10 @@ interface IVibrator {
* Whether perform w/ vendor effect is supported.
*/
const int CAP_PERFORM_VENDOR_EFFECTS = 1 << 11;
/**
* Whether composePwleV2 for PwlePrimitives is supported.
*/
const int CAP_COMPOSE_PWLE_EFFECTS_V2 = 1 << 12;
/**
* Determine capabilities of the vibrator HAL (CAP_* mask)
@@ -385,4 +391,82 @@ interface IVibrator {
* - EX_SERVICE_SPECIFIC for bad vendor data, vibration is not triggered.
*/
void performVendorEffect(in VendorEffect vendorEffect, in IVibratorCallback callback);
/**
* Retrieves a mapping of vibration frequency (Hz) to the maximum achievable output
* acceleration (Gs) the device can reach at that frequency.
*
* The map, represented as a list of `PwleV2OutputMapEntry` (frequency, output acceleration)
* pairs, defines the device's frequency response. The platform uses the minimum and maximum
* frequency values to determine the supported input range for `IVibrator.composePwleV2`.
* Output acceleration values are used to identify a frequency range suitable to safely play
* perceivable vibrations with a simple API. The map is also exposed for developers using an
* advanced API.
*
* The platform does not impose specific requirements on map resolution which can vary
* depending on the shape of device output curve. The values will be linearly interpolated
* during lookups. The platform will provide a simple API, defined by the first frequency range
* where output acceleration consistently exceeds a minimum threshold of 10 db SL.
*
*
* This may not be supported and this support is reflected in getCapabilities
* (CAP_COMPOSE_PWLE_EFFECTS_V2). If this is supported, it's expected to be non-empty and
* describe a valid non-empty frequency range where the simple API can be defined
* (i.e. a range where the output acceleration is always above 10 db SL).
*
* @return A list of map entries representing the frequency to max acceleration
* mapping.
* @throws EX_UNSUPPORTED_OPERATION if unsupported, as reflected by getCapabilities.
*/
List<PwleV2OutputMapEntry> getPwleV2FrequencyToOutputAccelerationMap();
/**
* Retrieve the maximum duration allowed for any primitive PWLE in units of
* milliseconds.
*
* This may not be supported and this support is reflected in
* getCapabilities (CAP_COMPOSE_PWLE_EFFECTS_V2).
*
* @return The maximum duration allowed for a single PrimitivePwle. Non-zero value if supported.
* @throws EX_UNSUPPORTED_OPERATION if unsupported, as reflected by getCapabilities.
*/
int getPwleV2PrimitiveDurationMaxMillis();
/**
* Retrieve the maximum number of PWLE primitives input supported by IVibrator.composePwleV2.
*
* This may not be supported and this support is reflected in
* getCapabilities (CAP_COMPOSE_PWLE_EFFECTS_V2). Devices supporting PWLE effects must
* support effects with at least 16 PwleV2Primitive.
*
* @return The maximum count allowed. Non-zero value if supported.
* @throws EX_UNSUPPORTED_OPERATION if unsupported, as reflected by getCapabilities.
*/
int getPwleV2CompositionSizeMax();
/**
* Retrieves the minimum duration (in milliseconds) of any segment within a
* PWLE effect. Devices supporting PWLE effects must support a minimum ramp
* time of 20 milliseconds.
*
* This may not be supported and this support is reflected in
* getCapabilities (CAP_COMPOSE_PWLE_EFFECTS_V2).
*
* @return The minimum duration allowed for a single PrimitivePwle. Non-zero value if supported.
* @throws EX_UNSUPPORTED_OPERATION if unsupported, as reflected by getCapabilities.
*/
int getPwleV2PrimitiveDurationMinMillis();
/**
* Play composed sequence of chirps with optional callback upon completion.
*
* This may not be supported and this support is reflected in
* getCapabilities (CAP_COMPOSE_PWLE_EFFECTS_V2).
*
* Doing this operation while the vibrator is already on is undefined behavior. Clients should
* explicitly call off. IVibratorCallback.onComplete() support is required for this API.
*
* @param composite An array of primitives that represents a PWLE (Piecewise-Linear Envelope).
*/
void composePwleV2(in PwleV2Primitive[] composite, in IVibratorCallback callback);
}

View File

@@ -0,0 +1,35 @@
/*
* 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 android.hardware.vibrator;
@VintfStability
parcelable PwleV2OutputMapEntry {
/**
* Absolute frequency point in the units of hertz
*
*/
float frequencyHz;
/**
* Max output acceleration for the specified frequency in units of Gs.
*
* This value represents the maximum safe output acceleration (in Gs) achievable at the
* specified frequency, typically determined during calibration. The actual output acceleration
* is assumed to scale linearly with the input amplitude within the range of [0, 1].
*/
float maxOutputAccelerationGs;
}

View File

@@ -0,0 +1,45 @@
/*
* 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 android.hardware.vibrator;
@VintfStability
parcelable PwleV2Primitive {
/**
* Input amplitude ranges from 0.0 (inclusive) to 1.0 (inclusive), representing the relative
* input value. Actual output acceleration depends on frequency and device response curve
* (see IVibrator.getPwleV2FrequencyToOutputAccelerationMap for max values).
*
* Input amplitude linearly maps to output acceleration (e.g., 0.5 amplitude yields half the
* max acceleration for that frequency).
*
* 0.0 represents no output acceleration amplitude
* 1.0 represents the maximum achievable strength for each frequency, as determined by the
* actuator response curve
*/
float amplitude;
/**
* Absolute frequency point in the units of hertz
*
* Values are within the continuous inclusive frequency range defined by
* IVibrator#getPwleV2FrequencyToOutputAccelerationMap.
*/
float frequencyHz;
/* Total time from the previous PWLE point to the current one in units of milliseconds. */
int timeMillis;
}

View File

@@ -27,9 +27,12 @@ namespace vibrator {
static constexpr int32_t COMPOSE_DELAY_MAX_MS = 1000;
static constexpr int32_t COMPOSE_SIZE_MAX = 256;
static constexpr int32_t COMPOSE_PWLE_SIZE_MAX = 127;
static constexpr int32_t COMPOSE_PWLE_V2_SIZE_MAX = 16;
static constexpr float Q_FACTOR = 11.0;
static constexpr int32_t COMPOSE_PWLE_PRIMITIVE_DURATION_MAX_MS = 16383;
static constexpr int32_t COMPOSE_PWLE_V2_PRIMITIVE_DURATION_MAX_MS = 1000;
static constexpr int32_t COMPOSE_PWLE_V2_PRIMITIVE_DURATION_MIN_MS = 20;
static constexpr float PWLE_LEVEL_MIN = 0.0;
static constexpr float PWLE_LEVEL_MAX = 1.0;
static constexpr float PWLE_FREQUENCY_RESOLUTION_HZ = 1.0;
@@ -44,12 +47,25 @@ static constexpr int32_t ERROR_CODE_INVALID_DURATION = 1;
ndk::ScopedAStatus Vibrator::getCapabilities(int32_t* _aidl_return) {
LOG(VERBOSE) << "Vibrator reporting capabilities";
*_aidl_return = IVibrator::CAP_ON_CALLBACK | IVibrator::CAP_PERFORM_CALLBACK |
IVibrator::CAP_AMPLITUDE_CONTROL | IVibrator::CAP_EXTERNAL_CONTROL |
IVibrator::CAP_EXTERNAL_AMPLITUDE_CONTROL | IVibrator::CAP_COMPOSE_EFFECTS |
IVibrator::CAP_ALWAYS_ON_CONTROL | IVibrator::CAP_GET_RESONANT_FREQUENCY |
IVibrator::CAP_GET_Q_FACTOR | IVibrator::CAP_FREQUENCY_CONTROL |
IVibrator::CAP_COMPOSE_PWLE_EFFECTS | IVibrator::CAP_PERFORM_VENDOR_EFFECTS;
std::lock_guard lock(mMutex);
if (mCapabilities == 0) {
if (!getInterfaceVersion(&mVersion).isOk()) {
return ndk::ScopedAStatus(AStatus_fromExceptionCode(EX_ILLEGAL_STATE));
}
mCapabilities = IVibrator::CAP_ON_CALLBACK | IVibrator::CAP_PERFORM_CALLBACK |
IVibrator::CAP_AMPLITUDE_CONTROL | IVibrator::CAP_EXTERNAL_CONTROL |
IVibrator::CAP_EXTERNAL_AMPLITUDE_CONTROL | IVibrator::CAP_COMPOSE_EFFECTS |
IVibrator::CAP_ALWAYS_ON_CONTROL | IVibrator::CAP_GET_RESONANT_FREQUENCY |
IVibrator::CAP_GET_Q_FACTOR | IVibrator::CAP_FREQUENCY_CONTROL |
IVibrator::CAP_COMPOSE_PWLE_EFFECTS;
if (mVersion >= 3) {
mCapabilities |= (IVibrator::CAP_PERFORM_VENDOR_EFFECTS |
IVibrator::CAP_COMPOSE_PWLE_EFFECTS_V2);
}
}
*_aidl_return = mCapabilities;
return ndk::ScopedAStatus::ok();
}
@@ -108,6 +124,13 @@ ndk::ScopedAStatus Vibrator::perform(Effect effect, EffectStrength strength,
ndk::ScopedAStatus Vibrator::performVendorEffect(
const VendorEffect& effect, const std::shared_ptr<IVibratorCallback>& callback) {
LOG(VERBOSE) << "Vibrator perform vendor effect";
int32_t capabilities = 0;
if (!getCapabilities(&capabilities).isOk()) {
return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE);
}
if ((capabilities & IVibrator::CAP_PERFORM_VENDOR_EFFECTS) == 0) {
return ndk::ScopedAStatus(AStatus_fromExceptionCode(EX_UNSUPPORTED_OPERATION));
}
EffectStrength strength = effect.strength;
if (strength != EffectStrength::LIGHT && strength != EffectStrength::MEDIUM &&
strength != EffectStrength::STRONG) {
@@ -449,6 +472,114 @@ ndk::ScopedAStatus Vibrator::composePwle(const std::vector<PrimitivePwle> &compo
return ndk::ScopedAStatus::ok();
}
ndk::ScopedAStatus Vibrator::getPwleV2FrequencyToOutputAccelerationMap(
std::vector<PwleV2OutputMapEntry>* _aidl_return) {
std::vector<PwleV2OutputMapEntry> frequencyToOutputAccelerationMap;
std::vector<std::pair<float, float>> frequencyToOutputAccelerationData = {
{30.0f, 0.01f}, {46.0f, 0.09f}, {50.0f, 0.1f}, {55.0f, 0.12f}, {62.0f, 0.66f},
{83.0f, 0.82f}, {85.0f, 0.85f}, {92.0f, 1.05f}, {107.0f, 1.63f}, {115.0f, 1.72f},
{123.0f, 1.81f}, {135.0f, 2.23f}, {144.0f, 2.47f}, {145.0f, 2.5f}, {150.0f, 3.0f},
{175.0f, 2.51f}, {181.0f, 2.41f}, {190.0f, 2.28f}, {200.0f, 2.08f}, {204.0f, 1.96f},
{205.0f, 1.9f}, {224.0f, 1.7f}, {235.0f, 1.5f}, {242.0f, 1.46f}, {253.0f, 1.41f},
{263.0f, 1.39f}, {65.0f, 1.38f}, {278.0f, 1.37f}, {294.0f, 1.35f}, {300.0f, 1.34f}};
for (const auto& entry : frequencyToOutputAccelerationData) {
frequencyToOutputAccelerationMap.push_back(
PwleV2OutputMapEntry(/*frequency=*/entry.first,
/*maxOutputAcceleration=*/entry.second));
}
*_aidl_return = frequencyToOutputAccelerationMap;
return ndk::ScopedAStatus::ok();
}
ndk::ScopedAStatus Vibrator::getPwleV2PrimitiveDurationMaxMillis(int32_t* maxDurationMs) {
*maxDurationMs = COMPOSE_PWLE_V2_PRIMITIVE_DURATION_MAX_MS;
return ndk::ScopedAStatus::ok();
}
ndk::ScopedAStatus Vibrator::getPwleV2CompositionSizeMax(int32_t* maxSize) {
*maxSize = COMPOSE_PWLE_V2_SIZE_MAX;
return ndk::ScopedAStatus::ok();
}
ndk::ScopedAStatus Vibrator::getPwleV2PrimitiveDurationMinMillis(int32_t* minDurationMs) {
*minDurationMs = COMPOSE_PWLE_V2_PRIMITIVE_DURATION_MIN_MS;
return ndk::ScopedAStatus::ok();
}
float getPwleV2FrequencyMinHz(std::vector<PwleV2OutputMapEntry> frequencyToOutputAccelerationMap) {
if (frequencyToOutputAccelerationMap.empty()) {
return 0.0f;
}
float minFrequency = frequencyToOutputAccelerationMap[0].frequencyHz;
for (const auto& entry : frequencyToOutputAccelerationMap) {
if (entry.frequencyHz < minFrequency) {
minFrequency = entry.frequencyHz;
}
}
return minFrequency;
}
float getPwleV2FrequencyMaxHz(std::vector<PwleV2OutputMapEntry> frequencyToOutputAccelerationMap) {
if (frequencyToOutputAccelerationMap.empty()) {
return 0.0f;
}
float maxFrequency = frequencyToOutputAccelerationMap[0].frequencyHz;
for (const auto& entry : frequencyToOutputAccelerationMap) {
if (entry.frequencyHz > maxFrequency) {
maxFrequency = entry.frequencyHz;
}
}
return maxFrequency;
}
ndk::ScopedAStatus Vibrator::composePwleV2(const std::vector<PwleV2Primitive>& composite,
const std::shared_ptr<IVibratorCallback>& callback) {
int compositionSizeMax;
getPwleV2CompositionSizeMax(&compositionSizeMax);
if (composite.size() <= 0 || composite.size() > compositionSizeMax) {
return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
}
int32_t totalEffectDuration = 0;
std::vector<PwleV2OutputMapEntry> frequencyToOutputAccelerationMap;
getPwleV2FrequencyToOutputAccelerationMap(&frequencyToOutputAccelerationMap);
float minFrequency = getPwleV2FrequencyMinHz(frequencyToOutputAccelerationMap);
float maxFrequency = getPwleV2FrequencyMaxHz(frequencyToOutputAccelerationMap);
for (auto& e : composite) {
if (e.timeMillis < 0.0f || e.timeMillis > COMPOSE_PWLE_V2_PRIMITIVE_DURATION_MAX_MS) {
return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
}
if (e.amplitude < 0.0f || e.amplitude > 1.0f) {
return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
}
if (e.frequencyHz < minFrequency || e.frequencyHz > maxFrequency) {
return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
}
totalEffectDuration += e.timeMillis;
}
std::thread([totalEffectDuration, callback] {
LOG(VERBOSE) << "Starting composePwleV2 on another thread";
usleep(totalEffectDuration * 1000);
if (callback != nullptr) {
LOG(VERBOSE) << "Notifying compose PWLE V2 complete";
callback->onComplete();
}
}).detach();
return ndk::ScopedAStatus::ok();
}
} // namespace vibrator
} // namespace hardware
} // namespace android

View File

@@ -17,6 +17,7 @@
#pragma once
#include <aidl/android/hardware/vibrator/BnVibrator.h>
#include <android-base/thread_annotations.h>
namespace aidl {
namespace android {
@@ -57,7 +58,18 @@ class Vibrator : public BnVibrator {
ndk::ScopedAStatus getSupportedBraking(std::vector<Braking>* supported) override;
ndk::ScopedAStatus composePwle(const std::vector<PrimitivePwle> &composite,
const std::shared_ptr<IVibratorCallback> &callback) override;
ndk::ScopedAStatus getPwleV2FrequencyToOutputAccelerationMap(
std::vector<PwleV2OutputMapEntry>* _aidl_return) override;
ndk::ScopedAStatus getPwleV2PrimitiveDurationMaxMillis(int32_t* maxDurationMs) override;
ndk::ScopedAStatus getPwleV2PrimitiveDurationMinMillis(int32_t* minDurationMs) override;
ndk::ScopedAStatus getPwleV2CompositionSizeMax(int32_t* maxSize) override;
ndk::ScopedAStatus composePwleV2(const std::vector<PwleV2Primitive>& composite,
const std::shared_ptr<IVibratorCallback>& callback) override;
private:
mutable std::mutex mMutex;
int32_t mVersion GUARDED_BY(mMutex) = 0; // current Hal version
int32_t mCapabilities GUARDED_BY(mMutex) = 0;
};
} // namespace vibrator

View File

@@ -27,8 +27,12 @@
#include <cstdlib>
#include <ctime>
#include <future>
#include <iomanip>
#include <iostream>
#include <random>
#include "persistable_bundle_utils.h"
#include "pwle_v2_utils.h"
#include "test_utils.h"
using aidl::android::hardware::vibrator::ActivePwle;
@@ -42,12 +46,16 @@ using aidl::android::hardware::vibrator::EffectStrength;
using aidl::android::hardware::vibrator::IVibrator;
using aidl::android::hardware::vibrator::IVibratorManager;
using aidl::android::hardware::vibrator::PrimitivePwle;
using aidl::android::hardware::vibrator::PwleV2OutputMapEntry;
using aidl::android::hardware::vibrator::PwleV2Primitive;
using aidl::android::hardware::vibrator::VendorEffect;
using aidl::android::os::PersistableBundle;
using std::chrono::high_resolution_clock;
using namespace ::std::chrono_literals;
namespace pwle_v2_utils = aidl::android::hardware::vibrator::testing::pwlev2;
const std::vector<Effect> kEffects{ndk::enum_range<Effect>().begin(),
ndk::enum_range<Effect>().end()};
const std::vector<EffectStrength> kEffectStrengths{ndk::enum_range<EffectStrength>().begin(),
@@ -80,6 +88,8 @@ const std::vector<CompositePrimitive> kInvalidPrimitives = {
// Timeout to wait for vibration callback completion.
static constexpr std::chrono::milliseconds VIBRATION_CALLBACK_TIMEOUT = 100ms;
static constexpr int32_t VENDOR_EFFECTS_MIN_VERSION = 3;
static std::vector<std::string> findVibratorManagerNames() {
std::vector<std::string> names;
constexpr auto callback = [](const char* instance, void* context) {
@@ -137,6 +147,7 @@ class VibratorAidl : public testing::TestWithParam<std::tuple<int32_t, int32_t>>
}
ASSERT_NE(vibrator, nullptr);
EXPECT_OK(vibrator->getInterfaceVersion(&version));
EXPECT_OK(vibrator->getCapabilities(&capabilities));
}
@@ -146,6 +157,7 @@ class VibratorAidl : public testing::TestWithParam<std::tuple<int32_t, int32_t>>
}
std::shared_ptr<IVibrator> vibrator;
int32_t version;
int32_t capabilities;
};
@@ -476,6 +488,10 @@ TEST_P(VibratorAidl, PerformVendorEffectInvalidScale) {
}
TEST_P(VibratorAidl, PerformVendorEffectUnsupported) {
if (version < VENDOR_EFFECTS_MIN_VERSION) {
EXPECT_EQ(capabilities & IVibrator::CAP_PERFORM_VENDOR_EFFECTS, 0)
<< "Vibrator version " << version << " should not report vendor effects capability";
}
if (capabilities & IVibrator::CAP_PERFORM_VENDOR_EFFECTS) return;
for (EffectStrength strength : kEffectStrengths) {
@@ -1035,6 +1051,156 @@ TEST_P(VibratorAidl, ComposePwleSegmentDurationBoundary) {
}
}
TEST_P(VibratorAidl, PwleV2FrequencyToOutputAccelerationMapHasValidFrequencyRange) {
std::vector<PwleV2OutputMapEntry> frequencyToOutputAccelerationMap;
ndk::ScopedAStatus status =
vibrator->getPwleV2FrequencyToOutputAccelerationMap(&frequencyToOutputAccelerationMap);
if (capabilities & IVibrator::CAP_COMPOSE_PWLE_EFFECTS_V2) {
EXPECT_OK(std::move(status));
ASSERT_FALSE(frequencyToOutputAccelerationMap.empty());
auto sharpnessRange =
pwle_v2_utils::getPwleV2SharpnessRange(vibrator, frequencyToOutputAccelerationMap);
// Validate the curve provides a usable sharpness range, which is a range of frequencies
// that are supported by the device.
ASSERT_TRUE(sharpnessRange.first >= 0);
// Validate that the sharpness range is a valid interval, not a single point.
ASSERT_TRUE(sharpnessRange.first < sharpnessRange.second);
} else {
EXPECT_UNKNOWN_OR_UNSUPPORTED(std::move(status));
}
}
TEST_P(VibratorAidl, GetPwleV2PrimitiveDurationMaxMillis) {
int32_t durationMs;
ndk::ScopedAStatus status = vibrator->getPwleV2PrimitiveDurationMaxMillis(&durationMs);
if (capabilities & IVibrator::CAP_COMPOSE_PWLE_EFFECTS_V2) {
EXPECT_OK(std::move(status));
ASSERT_GT(durationMs, 0); // Ensure greater than zero
ASSERT_GE(durationMs,
pwle_v2_utils::COMPOSE_PWLE_V2_MIN_REQUIRED_PRIMITIVE_MAX_DURATION_MS);
} else {
EXPECT_UNKNOWN_OR_UNSUPPORTED(std::move(status));
}
}
TEST_P(VibratorAidl, GetPwleV2CompositionSizeMax) {
int32_t maxSize;
ndk::ScopedAStatus status = vibrator->getPwleV2CompositionSizeMax(&maxSize);
if (capabilities & IVibrator::CAP_COMPOSE_PWLE_EFFECTS_V2) {
EXPECT_OK(std::move(status));
ASSERT_GT(maxSize, 0); // Ensure greater than zero
ASSERT_GE(maxSize, pwle_v2_utils::COMPOSE_PWLE_V2_MIN_REQUIRED_SIZE);
} else {
EXPECT_UNKNOWN_OR_UNSUPPORTED(std::move(status));
}
}
TEST_P(VibratorAidl, GetPwleV2PrimitiveDurationMinMillis) {
int32_t durationMs;
ndk::ScopedAStatus status = vibrator->getPwleV2PrimitiveDurationMinMillis(&durationMs);
if (capabilities & IVibrator::CAP_COMPOSE_PWLE_EFFECTS_V2) {
EXPECT_OK(std::move(status));
ASSERT_GT(durationMs, 0); // Ensure greater than zero
ASSERT_LE(durationMs, pwle_v2_utils::COMPOSE_PWLE_V2_MAX_ALLOWED_PRIMITIVE_MIN_DURATION_MS);
} else {
EXPECT_UNKNOWN_OR_UNSUPPORTED(std::move(status));
}
}
TEST_P(VibratorAidl, ComposeValidPwleV2Effect) {
if (!(capabilities & IVibrator::CAP_COMPOSE_PWLE_EFFECTS_V2)) {
GTEST_SKIP() << "PWLE V2 not supported, skipping test";
return;
}
EXPECT_OK(vibrator->composePwleV2(pwle_v2_utils::composeValidPwleV2Effect(vibrator), nullptr));
EXPECT_OK(vibrator->off());
}
TEST_P(VibratorAidl, ComposeValidPwleV2EffectWithCallback) {
if (!(capabilities & IVibrator::CAP_COMPOSE_PWLE_EFFECTS_V2)) {
GTEST_SKIP() << "PWLE V2 not supported, skipping test";
return;
}
std::promise<void> completionPromise;
std::future<void> completionFuture{completionPromise.get_future()};
auto callback = ndk::SharedRefBase::make<CompletionCallback>(
[&completionPromise] { completionPromise.set_value(); });
int32_t minDuration;
EXPECT_OK(vibrator->getPwleV2PrimitiveDurationMinMillis(&minDuration));
auto timeout = std::chrono::milliseconds(minDuration) + VIBRATION_CALLBACK_TIMEOUT;
float minFrequency = pwle_v2_utils::getPwleV2FrequencyMinHz(vibrator);
EXPECT_OK(vibrator->composePwleV2(
{PwleV2Primitive(/*amplitude=*/0.5, minFrequency, minDuration)}, callback));
EXPECT_EQ(completionFuture.wait_for(timeout), std::future_status::ready);
EXPECT_OK(vibrator->off());
}
TEST_P(VibratorAidl, composePwleV2EffectWithTooManyPoints) {
if (!(capabilities & IVibrator::CAP_COMPOSE_PWLE_EFFECTS_V2)) {
GTEST_SKIP() << "PWLE V2 not supported, skipping test";
return;
}
EXPECT_ILLEGAL_ARGUMENT(vibrator->composePwleV2(
pwle_v2_utils::composePwleV2EffectWithTooManyPoints(vibrator), nullptr));
}
TEST_P(VibratorAidl, composeInvalidPwleV2Effect) {
if (!(capabilities & IVibrator::CAP_COMPOSE_PWLE_EFFECTS_V2)) {
GTEST_SKIP() << "PWLE V2 not supported, skipping test";
return;
}
// Retrieve min and max durations
int32_t minDurationMs, maxDurationMs;
EXPECT_OK(vibrator->getPwleV2PrimitiveDurationMinMillis(&minDurationMs));
EXPECT_OK(vibrator->getPwleV2PrimitiveDurationMaxMillis(&maxDurationMs));
std::vector<PwleV2Primitive> composePwle;
// Negative amplitude
composePwle.push_back(PwleV2Primitive(/*amplitude=*/-0.8f, /*frequency=*/100, minDurationMs));
EXPECT_ILLEGAL_ARGUMENT(vibrator->composePwleV2(composePwle, nullptr))
<< "Composing PWLE V2 effect with negative amplitude should fail";
composePwle.clear();
// Amplitude exceeding 1.0
composePwle.push_back(PwleV2Primitive(/*amplitude=*/1.2f, /*frequency=*/100, minDurationMs));
EXPECT_ILLEGAL_ARGUMENT(vibrator->composePwleV2(composePwle, nullptr))
<< "Composing PWLE V2 effect with amplitude greater than 1.0 should fail";
composePwle.clear();
// Duration exceeding maximum
composePwle.push_back(
PwleV2Primitive(/*amplitude=*/0.2f, /*frequency=*/100, maxDurationMs + 10));
EXPECT_ILLEGAL_ARGUMENT(vibrator->composePwleV2(composePwle, nullptr))
<< "Composing PWLE V2 effect with duration exceeding maximum should fail";
composePwle.clear();
// Negative duration
composePwle.push_back(PwleV2Primitive(/*amplitude=*/0.2f, /*frequency=*/100, /*time=*/-1));
EXPECT_ILLEGAL_ARGUMENT(vibrator->composePwleV2(composePwle, nullptr))
<< "Composing PWLE V2 effect with negative duration should fail";
composePwle.clear();
// Frequency below minimum
float minFrequency = pwle_v2_utils::getPwleV2FrequencyMinHz(vibrator);
composePwle.push_back(PwleV2Primitive(/*amplitude=*/0.2f, minFrequency - 1, minDurationMs));
EXPECT_ILLEGAL_ARGUMENT(vibrator->composePwleV2(composePwle, nullptr))
<< "Composing PWLE V2 effect with frequency below minimum should fail";
composePwle.clear();
// Frequency above maximum
float maxFrequency = pwle_v2_utils::getPwleV2FrequencyMaxHz(vibrator);
composePwle.push_back(PwleV2Primitive(/*amplitude=*/0.2f, maxFrequency + 1, minDurationMs));
EXPECT_ILLEGAL_ARGUMENT(vibrator->composePwleV2(composePwle, nullptr))
<< "Composing PWLE V2 effect with frequency above maximum should fail";
}
std::vector<std::tuple<int32_t, int32_t>> GenerateVibratorMapping() {
std::vector<std::tuple<int32_t, int32_t>> tuples;

View File

@@ -0,0 +1,219 @@
/*
* 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.
*/
#ifndef VIBRATOR_HAL_PWLE_V2_UTILS_H
#define VIBRATOR_HAL_PWLE_V2_UTILS_H
#include <aidl/android/hardware/vibrator/IVibrator.h>
#include "test_utils.h"
using aidl::android::hardware::vibrator::IVibrator;
using aidl::android::hardware::vibrator::PwleV2OutputMapEntry;
using aidl::android::hardware::vibrator::PwleV2Primitive;
namespace aidl {
namespace android {
namespace hardware {
namespace vibrator {
namespace testing {
namespace pwlev2 {
static constexpr int32_t COMPOSE_PWLE_V2_MIN_REQUIRED_SIZE = 16;
static constexpr int32_t COMPOSE_PWLE_V2_MIN_REQUIRED_PRIMITIVE_MAX_DURATION_MS = 1000;
static constexpr int32_t COMPOSE_PWLE_V2_MAX_ALLOWED_PRIMITIVE_MIN_DURATION_MS = 20;
static constexpr int32_t COMPOSE_PWLE_V2_MIN_REQUIRED_SENSITIVITY_DB_SL = 10;
namespace {
/**
* Returns a vector of (frequency in Hz, acceleration in dB) pairs, where the acceleration
* value denotes the minimum output required at the corresponding frequency to be perceptible
* by a human.
*/
static std::vector<std::pair<float, float>> getMinPerceptibleLevel() {
return {{0.4f, -97.81f}, {2.0f, -69.86f}, {3.0f, -62.81f}, {4.0f, -58.81f},
{5.0f, -56.69f}, {6.0f, -54.77f}, {7.2f, -52.85f}, {8.0f, -51.77f},
{8.64f, -50.84f}, {10.0f, -48.90f}, {10.37f, -48.52f}, {12.44f, -46.50f},
{14.93f, -44.43f}, {15.0f, -44.35f}, {17.92f, -41.96f}, {20.0f, -40.36f},
{21.5f, -39.60f}, {25.0f, -37.48f}, {25.8f, -36.93f}, {30.0f, -34.31f},
{35.0f, -33.13f}, {40.0f, -32.81f}, {50.0f, -31.94f}, {60.0f, -31.77f},
{70.0f, -31.59f}, {72.0f, -31.55f}, {80.0f, -31.77f}, {86.4f, -31.94f},
{90.0f, -31.73f}, {100.0f, -31.90f}, {103.68f, -31.77f}, {124.42f, -31.70f},
{149.3f, -31.38f}, {150.0f, -31.35f}, {179.16f, -31.02f}, {200.0f, -30.86f},
{215.0f, -30.35f}, {250.0f, -28.98f}, {258.0f, -28.68f}, {300.0f, -26.81f},
{400.0f, -19.81f}};
}
static float interpolateLinearly(const std::vector<float>& xAxis, const std::vector<float>& yAxis,
float x) {
EXPECT_TRUE(!xAxis.empty());
EXPECT_TRUE(xAxis.size() == yAxis.size());
if (x <= xAxis.front()) return yAxis.front();
if (x >= xAxis.back()) return yAxis.back();
auto it = std::upper_bound(xAxis.begin(), xAxis.end(), x);
int i = std::distance(xAxis.begin(), it) - 1; // Index of the lower bound
const float& x0 = xAxis[i];
const float& y0 = yAxis[i];
const float& x1 = xAxis[i + 1];
const float& y1 = yAxis[i + 1];
return y0 + (x - x0) * (y1 - y0) / (x1 - x0);
}
static float minPerceptibleDbCurve(float frequency) {
// Initialize minPerceptibleMap only once
static auto minPerceptibleMap = []() -> std::function<float(float)> {
static std::vector<float> minPerceptibleFrequencies;
static std::vector<float> minPerceptibleAccelerations;
auto minPerceptibleLevel = getMinPerceptibleLevel();
// Sort the 'minPerceptibleLevel' data in ascending order based on the
// frequency values (first element of each pair).
std::sort(minPerceptibleLevel.begin(), minPerceptibleLevel.end(),
[](const auto& a, const auto& b) { return a.first < b.first; });
for (const auto& entry : minPerceptibleLevel) {
minPerceptibleFrequencies.push_back(entry.first);
minPerceptibleAccelerations.push_back(entry.second);
}
return [&](float freq) {
return interpolateLinearly(minPerceptibleFrequencies, minPerceptibleAccelerations,
freq);
};
}();
return minPerceptibleMap(frequency);
}
static float convertSensitivityLevelToDecibel(int sl, float frequency) {
return sl + minPerceptibleDbCurve(frequency);
}
static float convertDecibelToAcceleration(float db) {
return std::pow(10.0f, db / 20.0f);
}
} // namespace
static float convertSensitivityLevelToAcceleration(int sl, float frequency) {
return pwlev2::convertDecibelToAcceleration(
pwlev2::convertSensitivityLevelToDecibel(sl, frequency));
}
static float getPwleV2FrequencyMinHz(const std::shared_ptr<IVibrator>& vibrator) {
std::vector<PwleV2OutputMapEntry> frequencyToOutputAccelerationMap;
EXPECT_OK(
vibrator->getPwleV2FrequencyToOutputAccelerationMap(&frequencyToOutputAccelerationMap));
EXPECT_TRUE(!frequencyToOutputAccelerationMap.empty());
auto entry = std::min_element(
frequencyToOutputAccelerationMap.begin(), frequencyToOutputAccelerationMap.end(),
[](const auto& a, const auto& b) { return a.frequencyHz < b.frequencyHz; });
return entry->frequencyHz;
}
static float getPwleV2FrequencyMaxHz(const std::shared_ptr<IVibrator>& vibrator) {
std::vector<PwleV2OutputMapEntry> frequencyToOutputAccelerationMap;
EXPECT_OK(
vibrator->getPwleV2FrequencyToOutputAccelerationMap(&frequencyToOutputAccelerationMap));
EXPECT_TRUE(!frequencyToOutputAccelerationMap.empty());
auto entry = std::max_element(
frequencyToOutputAccelerationMap.begin(), frequencyToOutputAccelerationMap.end(),
[](const auto& a, const auto& b) { return a.frequencyHz < b.frequencyHz; });
return entry->frequencyHz;
}
static std::vector<PwleV2Primitive> composeValidPwleV2Effect(
const std::shared_ptr<IVibrator>& vibrator) {
int32_t minDurationMs;
EXPECT_OK(vibrator->getPwleV2PrimitiveDurationMinMillis(&minDurationMs));
int32_t maxDurationMs;
EXPECT_OK(vibrator->getPwleV2PrimitiveDurationMaxMillis(&maxDurationMs));
float minFrequency = getPwleV2FrequencyMinHz(vibrator);
float maxFrequency = getPwleV2FrequencyMaxHz(vibrator);
int32_t maxCompositionSize;
EXPECT_OK(vibrator->getPwleV2CompositionSizeMax(&maxCompositionSize));
std::vector<PwleV2Primitive> pwleEffect;
pwleEffect.emplace_back(0.1f, minFrequency, minDurationMs);
pwleEffect.emplace_back(0.5f, maxFrequency, maxDurationMs);
float variedFrequency = (minFrequency + maxFrequency) / 2.0f;
for (int i = 0; i < maxCompositionSize - 2; i++) {
pwleEffect.emplace_back(0.7f, variedFrequency, minDurationMs);
}
return pwleEffect;
}
static std::vector<PwleV2Primitive> composePwleV2EffectWithTooManyPoints(
const std::shared_ptr<IVibrator>& vibrator) {
int32_t minDurationMs, maxCompositionSize;
EXPECT_OK(vibrator->getPwleV2PrimitiveDurationMinMillis(&minDurationMs));
EXPECT_OK(vibrator->getPwleV2CompositionSizeMax(&maxCompositionSize));
float maxFrequency = getPwleV2FrequencyMaxHz(vibrator);
std::vector<PwleV2Primitive> pwleEffect(maxCompositionSize + 1); // +1 to exceed the limit
std::fill(pwleEffect.begin(), pwleEffect.end(),
PwleV2Primitive(/*amplitude=*/0.2f, maxFrequency, minDurationMs));
return pwleEffect;
}
static std::pair<float, float> getPwleV2SharpnessRange(
const std::shared_ptr<IVibrator>& vibrator,
std::vector<PwleV2OutputMapEntry> freqToOutputAccelerationMap) {
std::pair<float, float> sharpnessRange = {-1, -1};
// Sort the entries by frequency in ascending order
std::sort(freqToOutputAccelerationMap.begin(), freqToOutputAccelerationMap.end(),
[](const auto& a, const auto& b) { return a.frequencyHz < b.frequencyHz; });
for (const auto& entry : freqToOutputAccelerationMap) {
float minAcceptableOutputAcceleration = convertSensitivityLevelToAcceleration(
pwlev2::COMPOSE_PWLE_V2_MIN_REQUIRED_SENSITIVITY_DB_SL, entry.frequencyHz);
if (sharpnessRange.first < 0 &&
minAcceptableOutputAcceleration <= entry.maxOutputAccelerationGs) {
sharpnessRange.first = entry.frequencyHz; // Found the lower bound
} else if (sharpnessRange.first >= 0 &&
minAcceptableOutputAcceleration >= entry.maxOutputAccelerationGs) {
sharpnessRange.second = entry.frequencyHz; // Found the upper bound
return sharpnessRange;
}
}
if (sharpnessRange.first >= 0) {
// If only the lower bound was found, set the upper bound to the max frequency.
sharpnessRange.second = getPwleV2FrequencyMaxHz(vibrator);
}
return sharpnessRange;
}
} // namespace pwlev2
} // namespace testing
} // namespace vibrator
} // namespace hardware
} // namespace android
} // namespace aidl
#endif // VIBRATOR_HAL_PWLE_V2_UTILS_H