Introduce IVibrator.performVendorEffect
Introduce HAL API to perform vibration effects defined by the vendor.
The new API accepts a PersistableBundle representing the effect and some
platform-provided parameters, e.g. EffectStrength and scale.
The callback support for completion is required for this API, since
effect duration is undefined.
Fix: 345409060
Test: VtsHalVibratorTargetTest
Flag: EXEMPT HAL interface change
Change-Id: I26379ede4b64e41e1fc85feae6de5105cb636511
diff --git a/vibrator/aidl/vts/Android.bp b/vibrator/aidl/vts/Android.bp
index 166b30b..a48bb2e 100644
--- a/vibrator/aidl/vts/Android.bp
+++ b/vibrator/aidl/vts/Android.bp
@@ -20,7 +20,7 @@
"libbinder_ndk",
],
static_libs: [
- "android.hardware.vibrator-V2-ndk",
+ "android.hardware.vibrator-V3-ndk",
],
test_suites: [
"general-tests",
@@ -39,7 +39,7 @@
"libbinder_ndk",
],
static_libs: [
- "android.hardware.vibrator-V2-ndk",
+ "android.hardware.vibrator-V3-ndk",
],
test_suites: [
"general-tests",
diff --git a/vibrator/aidl/vts/VtsHalVibratorTargetTest.cpp b/vibrator/aidl/vts/VtsHalVibratorTargetTest.cpp
index 65a1e84..706ab41 100644
--- a/vibrator/aidl/vts/VtsHalVibratorTargetTest.cpp
+++ b/vibrator/aidl/vts/VtsHalVibratorTargetTest.cpp
@@ -21,10 +21,14 @@
#include <android/binder_manager.h>
#include <android/binder_process.h>
+#include <android/persistable_bundle_aidl.h>
#include <cmath>
+#include <cstdlib>
+#include <ctime>
#include <future>
+#include "persistable_bundle_utils.h"
#include "test_utils.h"
using aidl::android::hardware::vibrator::ActivePwle;
@@ -38,6 +42,8 @@
using aidl::android::hardware::vibrator::IVibrator;
using aidl::android::hardware::vibrator::IVibratorManager;
using aidl::android::hardware::vibrator::PrimitivePwle;
+using aidl::android::hardware::vibrator::VendorEffect;
+using aidl::android::os::PersistableBundle;
using std::chrono::high_resolution_clock;
using namespace ::std::chrono_literals;
@@ -357,6 +363,122 @@
}
}
+TEST_P(VibratorAidl, PerformVendorEffectSupported) {
+ if ((capabilities & IVibrator::CAP_PERFORM_VENDOR_EFFECTS) == 0) return;
+
+ float scale = 0.5f;
+ for (EffectStrength strength : kEffectStrengths) {
+ PersistableBundle vendorData;
+ ::aidl::android::hardware::vibrator::testing::fillBasicData(&vendorData);
+
+ PersistableBundle nestedData;
+ ::aidl::android::hardware::vibrator::testing::fillBasicData(&nestedData);
+ vendorData.putPersistableBundle("test_nested_bundle", nestedData);
+
+ VendorEffect effect;
+ effect.vendorData = vendorData;
+ effect.strength = strength;
+ effect.scale = scale;
+ scale *= 1.5f;
+
+ auto callback = ndk::SharedRefBase::make<CompletionCallback>([] {});
+ ndk::ScopedAStatus status = vibrator->performVendorEffect(effect, callback);
+
+ // No expectations on the actual status, the effect might be refused with illegal argument
+ // or the vendor might return a service-specific error code.
+ EXPECT_TRUE(status.getExceptionCode() != EX_UNSUPPORTED_OPERATION &&
+ status.getStatus() != STATUS_UNKNOWN_TRANSACTION)
+ << status << "\n For vendor effect with strength" << toString(strength)
+ << " and scale " << effect.scale;
+
+ if (status.isOk()) {
+ // Generic vendor data should not trigger vibrations, but if it does trigger one
+ // then we make sure the vibrator is reset by triggering off().
+ EXPECT_OK(vibrator->off());
+ }
+ }
+}
+
+TEST_P(VibratorAidl, PerformVendorEffectStability) {
+ if ((capabilities & IVibrator::CAP_PERFORM_VENDOR_EFFECTS) == 0) return;
+
+ // Run some iterations of performVendorEffect with randomized vendor data to check basic
+ // stability of the implementation.
+ uint8_t iterations = 200;
+
+ for (EffectStrength strength : kEffectStrengths) {
+ float scale = 0.5f;
+ for (uint8_t i = 0; i < iterations; i++) {
+ PersistableBundle vendorData;
+ ::aidl::android::hardware::vibrator::testing::fillRandomData(&vendorData);
+
+ VendorEffect effect;
+ effect.vendorData = vendorData;
+ effect.strength = strength;
+ effect.scale = scale;
+ scale *= 2;
+
+ auto callback = ndk::SharedRefBase::make<CompletionCallback>([] {});
+ ndk::ScopedAStatus status = vibrator->performVendorEffect(effect, callback);
+
+ // No expectations on the actual status, the effect might be refused with illegal
+ // argument or the vendor might return a service-specific error code.
+ EXPECT_TRUE(status.getExceptionCode() != EX_UNSUPPORTED_OPERATION &&
+ status.getStatus() != STATUS_UNKNOWN_TRANSACTION)
+ << status << "\n For random vendor effect with strength " << toString(strength)
+ << " and scale " << effect.scale;
+
+ if (status.isOk()) {
+ // Random vendor data should not trigger vibrations, but if it does trigger one
+ // then we make sure the vibrator is reset by triggering off().
+ EXPECT_OK(vibrator->off());
+ }
+ }
+ }
+}
+
+TEST_P(VibratorAidl, PerformVendorEffectEmptyVendorData) {
+ if ((capabilities & IVibrator::CAP_PERFORM_VENDOR_EFFECTS) == 0) return;
+
+ for (EffectStrength strength : kEffectStrengths) {
+ VendorEffect effect;
+ effect.strength = strength;
+ effect.scale = 1.0f;
+
+ ndk::ScopedAStatus status = vibrator->performVendorEffect(effect, nullptr /*callback*/);
+
+ EXPECT_TRUE(status.getExceptionCode() == EX_SERVICE_SPECIFIC)
+ << status << "\n For vendor effect with strength " << toString(strength)
+ << " and scale " << effect.scale;
+ }
+}
+
+TEST_P(VibratorAidl, PerformVendorEffectInvalidScale) {
+ if ((capabilities & IVibrator::CAP_PERFORM_VENDOR_EFFECTS) == 0) return;
+
+ VendorEffect effect;
+ effect.strength = EffectStrength::MEDIUM;
+
+ effect.scale = 0.0f;
+ EXPECT_ILLEGAL_ARGUMENT(vibrator->performVendorEffect(effect, nullptr /*callback*/));
+
+ effect.scale = -1.0f;
+ EXPECT_ILLEGAL_ARGUMENT(vibrator->performVendorEffect(effect, nullptr /*callback*/));
+}
+
+TEST_P(VibratorAidl, PerformVendorEffectUnsupported) {
+ if (capabilities & IVibrator::CAP_PERFORM_VENDOR_EFFECTS) return;
+
+ for (EffectStrength strength : kEffectStrengths) {
+ VendorEffect effect;
+ effect.strength = strength;
+ effect.scale = 1.0f;
+
+ EXPECT_UNKNOWN_OR_UNSUPPORTED(vibrator->performVendorEffect(effect, nullptr /*callback*/))
+ << "\n For vendor effect with strength " << toString(strength);
+ }
+}
+
TEST_P(VibratorAidl, ChangeVibrationAmplitude) {
if (capabilities & IVibrator::CAP_AMPLITUDE_CONTROL) {
EXPECT_OK(vibrator->setAmplitude(0.1f));
@@ -940,6 +1062,9 @@
PrintGeneratedTest);
int main(int argc, char **argv) {
+ // Random values are used in the implementation.
+ std::srand(std::time(nullptr));
+
::testing::InitGoogleTest(&argc, argv);
ABinderProcess_setThreadPoolMaxThreadCount(1);
ABinderProcess_startThreadPool();
diff --git a/vibrator/aidl/vts/persistable_bundle_utils.h b/vibrator/aidl/vts/persistable_bundle_utils.h
new file mode 100644
index 0000000..a765a49
--- /dev/null
+++ b/vibrator/aidl/vts/persistable_bundle_utils.h
@@ -0,0 +1,134 @@
+/*
+ * 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_PERSISTABLE_BUNDLE_UTILS_H
+#define VIBRATOR_HAL_PERSISTABLE_BUNDLE_UTILS_H
+
+#include <android/persistable_bundle_aidl.h>
+
+#include <cstdlib>
+
+namespace aidl {
+namespace android {
+namespace hardware {
+namespace vibrator {
+namespace testing {
+
+using aidl::android::os::PersistableBundle;
+
+namespace {
+
+template <typename T>
+T nextValue() {
+ return static_cast<T>(std::rand());
+}
+
+template <>
+std::string nextValue() {
+ std::string str;
+ uint8_t entryCount = nextValue<uint8_t>();
+ for (uint8_t i = 0; i < entryCount; i++) {
+ str.push_back(nextValue<char>());
+ }
+ return str;
+}
+
+template <typename T>
+T nextValue(T limit) {
+ assert(limit > 0);
+ return static_cast<T>(std::rand()) / (static_cast<T>(RAND_MAX / limit));
+}
+
+template <typename T>
+void fillVector(std::vector<T>* values) {
+ uint8_t entryCount = nextValue<uint8_t>();
+ for (uint8_t i = 0; i < entryCount; i++) {
+ values->push_back(nextValue<T>());
+ }
+}
+
+const std::vector<std::function<void(PersistableBundle*, const std::string&)>>
+ sPersistableBundleSetters = {[](PersistableBundle* bundle, const std::string& key) -> void {
+ bundle->putBoolean(key, nextValue<bool>());
+ },
+ [](PersistableBundle* bundle, const std::string& key) -> void {
+ bundle->putInt(key, nextValue<int32_t>());
+ },
+ [](PersistableBundle* bundle, const std::string& key) -> void {
+ bundle->putLong(key, nextValue<int64_t>());
+ },
+ [](PersistableBundle* bundle, const std::string& key) -> void {
+ bundle->putDouble(key, nextValue<double>());
+ },
+ [](PersistableBundle* bundle, const std::string& key) -> void {
+ bundle->putString(key, nextValue<std::string>());
+ },
+ [](PersistableBundle* bundle, const std::string& key) -> void {
+ std::vector<bool> value;
+ fillVector<bool>(&value);
+ bundle->putBooleanVector(key, value);
+ },
+ [](PersistableBundle* bundle, const std::string& key) -> void {
+ std::vector<int32_t> value;
+ fillVector<int32_t>(&value);
+ bundle->putIntVector(key, value);
+ },
+ [](PersistableBundle* bundle, const std::string& key) -> void {
+ std::vector<int64_t> value;
+ fillVector<int64_t>(&value);
+ bundle->putLongVector(key, value);
+ },
+ [](PersistableBundle* bundle, const std::string& key) -> void {
+ std::vector<double> value;
+ fillVector<double>(&value);
+ bundle->putDoubleVector(key, value);
+ },
+ [](PersistableBundle* bundle, const std::string& key) -> void {
+ std::vector<std::string> value;
+ fillVector<std::string>(&value);
+ bundle->putStringVector(key, value);
+ }};
+
+} // namespace
+
+void fillBasicData(PersistableBundle* bundle) {
+ bundle->putBoolean("test_bool", true);
+ bundle->putInt("test_int", 2147483647);
+ bundle->putLong("test_long", 2147483647L);
+ bundle->putDouble("test_double", 1.23);
+ bundle->putString("test_string", "test data");
+ bundle->putBooleanVector("test_bool_vector", {true, false, false});
+ bundle->putIntVector("test_int_vector", {1, 2, 3, 4});
+ bundle->putLongVector("test_long_vector", {100L, 200L, 300L});
+ bundle->putDoubleVector("test_double_vector", {1.1, 2.2});
+ bundle->putStringVector("test_string_vector", {"test", "val"});
+}
+
+void fillRandomData(PersistableBundle* bundle) {
+ uint8_t entryCount = nextValue<uint8_t>();
+ for (uint8_t i = 0; i < entryCount; i++) {
+ std::string key(nextValue<std::string>());
+ uint8_t setterIdx = nextValue<uint8_t>(sPersistableBundleSetters.size() - 1);
+ sPersistableBundleSetters[setterIdx](bundle, key);
+ }
+}
+
+} // namespace testing
+} // namespace vibrator
+} // namespace hardware
+} // namespace android
+} // namespace aidl
+
+#endif // VIBRATOR_HAL_PERSISTABLE_BUNDLE_UTILS_H