diff --git a/vibrator/aidl/android/hardware/vibrator/CompositePrimitive.aidl b/vibrator/aidl/android/hardware/vibrator/CompositePrimitive.aidl index b9a80ec116..0fdfa5d5c2 100644 --- a/vibrator/aidl/android/hardware/vibrator/CompositePrimitive.aidl +++ b/vibrator/aidl/android/hardware/vibrator/CompositePrimitive.aidl @@ -49,4 +49,9 @@ enum CompositePrimitive { * A haptic effect that simulates quick downwards movement with gravity. */ QUICK_FALL, + /** + * This very short effect should produce a light crisp sensation intended + * to be used repetitively for dynamic feedback. + */ + LIGHT_TICK, } diff --git a/vibrator/aidl/android/hardware/vibrator/IVibrator.aidl b/vibrator/aidl/android/hardware/vibrator/IVibrator.aidl index f553664eeb..06a8bf5c17 100644 --- a/vibrator/aidl/android/hardware/vibrator/IVibrator.aidl +++ b/vibrator/aidl/android/hardware/vibrator/IVibrator.aidl @@ -157,13 +157,32 @@ interface IVibrator { */ int getCompositionSizeMax(); + /** + * List of supported effect primitive. + * + * Return the effect primitives which are supported by the compose API. + * Implementations are expected to support all primitives of the interface + * version that they implement. + */ + CompositePrimitive[] getSupportedPrimitives(); + + /** + * Retrieve effect primitive's duration in milliseconds. + * + * Support is reflected in getCapabilities (CAP_COMPOSE_EFFECTS). + * + * @return Best effort estimation of effect primitive's duration. + * @param primitive Effect primitive being queried. + */ + int getPrimitiveDuration(CompositePrimitive primitive); + /** * Fire off a string of effect primitives, combined to perform richer effects. * * Support is reflected in getCapabilities (CAP_COMPOSE_EFFECTS). * * Doing this operation while the vibrator is already on is undefined behavior. Clients should - * explicitly call off. + * explicitly call off. IVibratorCallback.onComplete() support is required for this API. * * @param composite Array of composition parameters. */ diff --git a/vibrator/aidl/default/Vibrator.cpp b/vibrator/aidl/default/Vibrator.cpp index cedd9cb89c..0d7131aaf8 100644 --- a/vibrator/aidl/default/Vibrator.cpp +++ b/vibrator/aidl/default/Vibrator.cpp @@ -113,6 +113,26 @@ ndk::ScopedAStatus Vibrator::getCompositionSizeMax(int32_t* maxSize) { return ndk::ScopedAStatus::ok(); } +ndk::ScopedAStatus Vibrator::getSupportedPrimitives(std::vector* supported) { + *supported = { + CompositePrimitive::NOOP, CompositePrimitive::CLICK, + CompositePrimitive::THUD, CompositePrimitive::SPIN, + CompositePrimitive::QUICK_RISE, CompositePrimitive::SLOW_RISE, + CompositePrimitive::QUICK_FALL, CompositePrimitive::LIGHT_TICK, + }; + return ndk::ScopedAStatus::ok(); +} + +ndk::ScopedAStatus Vibrator::getPrimitiveDuration(CompositePrimitive primitive, + int32_t* durationMs) { + if (primitive != CompositePrimitive::NOOP) { + *durationMs = 100; + } else { + *durationMs = 0; + } + return ndk::ScopedAStatus::ok(); +} + ndk::ScopedAStatus Vibrator::compose(const std::vector& composite, const std::shared_ptr& callback) { if (composite.size() > kComposeSizeMax) { @@ -127,7 +147,7 @@ ndk::ScopedAStatus Vibrator::compose(const std::vector& composi return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT); } if (e.primitive < CompositePrimitive::NOOP || - e.primitive > CompositePrimitive::QUICK_FALL) { + e.primitive > CompositePrimitive::LIGHT_TICK) { return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION); } } diff --git a/vibrator/aidl/default/include/vibrator-impl/Vibrator.h b/vibrator/aidl/default/include/vibrator-impl/Vibrator.h index 0eb957d9fa..c3f3616876 100644 --- a/vibrator/aidl/default/include/vibrator-impl/Vibrator.h +++ b/vibrator/aidl/default/include/vibrator-impl/Vibrator.h @@ -36,6 +36,9 @@ class Vibrator : public BnVibrator { ndk::ScopedAStatus setExternalControl(bool enabled) override; ndk::ScopedAStatus getCompositionDelayMax(int32_t* maxDelayMs); ndk::ScopedAStatus getCompositionSizeMax(int32_t* maxSize); + ndk::ScopedAStatus getSupportedPrimitives(std::vector* supported) override; + ndk::ScopedAStatus getPrimitiveDuration(CompositePrimitive primitive, + int32_t* durationMs) override; ndk::ScopedAStatus compose(const std::vector& composite, const std::shared_ptr& callback) override; ndk::ScopedAStatus getSupportedAlwaysOnEffects(std::vector* _aidl_return) override; diff --git a/vibrator/aidl/vts/VtsHalVibratorTargetTest.cpp b/vibrator/aidl/vts/VtsHalVibratorTargetTest.cpp index f197763be3..411fe7a2fe 100644 --- a/vibrator/aidl/vts/VtsHalVibratorTargetTest.cpp +++ b/vibrator/aidl/vts/VtsHalVibratorTargetTest.cpp @@ -261,6 +261,29 @@ TEST_P(VibratorAidl, ExternalControlUnsupportedMatchingCapabilities) { } } +TEST_P(VibratorAidl, GetSupportedPrimitives) { + if (capabilities & IVibrator::CAP_COMPOSE_EFFECTS) { + std::vector supported; + + EXPECT_EQ(Status::EX_NONE, vibrator->getSupportedPrimitives(&supported).exceptionCode()); + + std::sort(supported.begin(), supported.end()); + + EXPECT_EQ(kCompositePrimitives, supported); + } +} + +TEST_P(VibratorAidl, GetPrimitiveDuration) { + if (capabilities & IVibrator::CAP_COMPOSE_EFFECTS) { + int32_t duration; + + for (auto primitive : kCompositePrimitives) { + EXPECT_EQ(Status::EX_NONE, + vibrator->getPrimitiveDuration(primitive, &duration).exceptionCode()); + } + } +} + TEST_P(VibratorAidl, ComposeValidPrimitives) { if (capabilities & IVibrator::CAP_COMPOSE_EFFECTS) { int32_t maxDelay, maxSize; @@ -357,6 +380,30 @@ TEST_P(VibratorAidl, CompseSizeBoundary) { } } +TEST_P(VibratorAidl, ComposeCallback) { + if (capabilities & IVibrator::CAP_COMPOSE_EFFECTS) { + std::promise completionPromise; + std::future completionFuture{completionPromise.get_future()}; + sp callback = + new CompletionCallback([&completionPromise] { completionPromise.set_value(); }); + CompositePrimitive primitive = CompositePrimitive::CLICK; + CompositeEffect effect; + std::vector composite; + int32_t duration; + + effect.delayMs = 0; + effect.primitive = primitive; + effect.scale = 1.0f; + composite.emplace_back(effect); + + EXPECT_EQ(Status::EX_NONE, + vibrator->getPrimitiveDuration(primitive, &duration).exceptionCode()); + EXPECT_EQ(Status::EX_NONE, vibrator->compose(composite, callback).exceptionCode()); + EXPECT_EQ(completionFuture.wait_for(std::chrono::milliseconds(duration * 2)), + std::future_status::ready); + } +} + TEST_P(VibratorAidl, AlwaysOn) { if (capabilities & IVibrator::CAP_ALWAYS_ON_CONTROL) { std::vector supported;