diff --git a/vibrator/aidl/android/hardware/vibrator/CompositeEffect.aidl b/vibrator/aidl/android/hardware/vibrator/CompositeEffect.aidl new file mode 100644 index 0000000000..84556b57bb --- /dev/null +++ b/vibrator/aidl/android/hardware/vibrator/CompositeEffect.aidl @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2019 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; + +import android.hardware.vibrator.CompositePrimitive; + +@VintfStability +parcelable CompositeEffect { + /* Period of silence preceding primitive. */ + int delayMs; + CompositePrimitive primitive; + /* 0.0 (exclusive) - 1.0 (inclusive) */ + float scale; +} diff --git a/vibrator/aidl/android/hardware/vibrator/CompositePrimitive.aidl b/vibrator/aidl/android/hardware/vibrator/CompositePrimitive.aidl new file mode 100644 index 0000000000..2a9d0be31d --- /dev/null +++ b/vibrator/aidl/android/hardware/vibrator/CompositePrimitive.aidl @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2019 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 +@Backing(type="int") +enum CompositePrimitive { + NOOP, + CLICK, + THUD, + SPIN, + QUICK_RISE, + SLOW_RISE, + QUICK_FALL, +} diff --git a/vibrator/aidl/android/hardware/vibrator/IVibrator.aidl b/vibrator/aidl/android/hardware/vibrator/IVibrator.aidl index 8c4fd055c3..ebf5faa47a 100644 --- a/vibrator/aidl/android/hardware/vibrator/IVibrator.aidl +++ b/vibrator/aidl/android/hardware/vibrator/IVibrator.aidl @@ -19,6 +19,8 @@ package android.hardware.vibrator; import android.hardware.vibrator.IVibratorCallback; import android.hardware.vibrator.Effect; import android.hardware.vibrator.EffectStrength; +import android.hardware.vibrator.CompositeEffect; +import android.hardware.vibrator.CompositePrimitive; @VintfStability interface IVibrator { @@ -42,6 +44,10 @@ interface IVibrator { * Whether setAmplitude is supported (when external control is enabled) */ const int CAP_EXTERNAL_AMPLITUDE_CONTROL = 1 << 4; + /** + * Whether compose is supported. + */ + const int CAP_COMPOSE_EFFECTS = 1 << 5; /** * Determine capabilities of the vibrator HAL (CAP_* mask) @@ -107,11 +113,10 @@ interface IVibrator { * CAP_EXTERNAL_AMPLITUDE_CONTROL. * * @param amplitude The unitless force setting. Note that this number must - * be between 1 and 255, inclusive. If the motor does not - * have exactly 255 steps, it must do it's best to map it - * onto the number of steps it does have. + * be between 0.0 (exclusive) and 1.0 (inclusive). It must + * do it's best to map it onto the number of steps it does have. */ - void setAmplitude(in int amplitude); + void setAmplitude(in float amplitude); /** * Enables/disables control override of vibrator to audio. @@ -128,4 +133,36 @@ interface IVibrator { * @param enabled Whether external control should be enabled or disabled. */ void setExternalControl(in boolean enabled); + + /** + * Retrieve composition delay limit. + * + * Support is reflected in getCapabilities (CAP_COMPOSE_EFFECTS). + * + * @return Maximum delay for a single CompositeEffect[] entry. + */ + int getCompositionDelayMax(); + + /** + * Retrieve composition size limit. + * + * Support is reflected in getCapabilities (CAP_COMPOSE_EFFECTS). + * + * @return Maximum number of entries in CompositeEffect[]. + * @param maxDelayMs Maximum delay for a single CompositeEffect[] entry. + */ + int getCompositionSizeMax(); + + /** + * 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. + * + * @param composite Array of composition parameters. + */ + void compose(in CompositeEffect[] composite, in IVibratorCallback callback); + } diff --git a/vibrator/aidl/default/Vibrator.cpp b/vibrator/aidl/default/Vibrator.cpp index 09cd2345bf..a77c49a0a2 100644 --- a/vibrator/aidl/default/Vibrator.cpp +++ b/vibrator/aidl/default/Vibrator.cpp @@ -24,11 +24,14 @@ namespace android { namespace hardware { namespace vibrator { +static constexpr int32_t kComposeDelayMaxMs = 1000; +static constexpr int32_t kComposeSizeMax = 256; + ndk::ScopedAStatus Vibrator::getCapabilities(int32_t* _aidl_return) { LOG(INFO) << "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_EXTERNAL_AMPLITUDE_CONTROL | IVibrator::CAP_COMPOSE_EFFECTS; return ndk::ScopedAStatus::ok(); } @@ -84,9 +87,9 @@ ndk::ScopedAStatus Vibrator::getSupportedEffects(std::vector* _aidl_retu return ndk::ScopedAStatus::ok(); } -ndk::ScopedAStatus Vibrator::setAmplitude(int32_t amplitude) { +ndk::ScopedAStatus Vibrator::setAmplitude(float amplitude) { LOG(INFO) << "Vibrator set amplitude: " << amplitude; - if (amplitude <= 0 || amplitude > 255) { + if (amplitude <= 0.0f || amplitude > 1.0f) { return ndk::ScopedAStatus(AStatus_fromExceptionCode(EX_ILLEGAL_ARGUMENT)); } return ndk::ScopedAStatus::ok(); @@ -97,6 +100,55 @@ ndk::ScopedAStatus Vibrator::setExternalControl(bool enabled) { return ndk::ScopedAStatus::ok(); } +ndk::ScopedAStatus Vibrator::getCompositionDelayMax(int32_t* maxDelayMs) { + *maxDelayMs = kComposeDelayMaxMs; + return ndk::ScopedAStatus::ok(); +} + +ndk::ScopedAStatus Vibrator::getCompositionSizeMax(int32_t* maxSize) { + *maxSize = kComposeSizeMax; + return ndk::ScopedAStatus::ok(); +} + +ndk::ScopedAStatus Vibrator::compose(const std::vector& composite, + const std::shared_ptr& callback) { + if (composite.size() > kComposeSizeMax) { + return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT); + } + + for (auto& e : composite) { + if (e.delayMs > kComposeDelayMaxMs) { + return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT); + } + if (e.scale <= 0.0f || e.scale > 1.0f) { + return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT); + } + if (e.primitive < CompositePrimitive::NOOP || + e.primitive > CompositePrimitive::QUICK_FALL) { + return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION); + } + } + + std::thread([=] { + LOG(INFO) << "Starting compose on another thread"; + + for (auto& e : composite) { + if (e.delayMs) { + usleep(e.delayMs * 1000); + } + LOG(INFO) << "triggering primitive " << static_cast(e.primitive) << " @ scale " + << e.scale; + } + + if (callback != nullptr) { + LOG(INFO) << "Notifying perform complete"; + callback->onComplete(); + } + }).detach(); + + return ndk::ScopedAStatus::ok(); +} + } // namespace vibrator } // namespace hardware } // namespace android diff --git a/vibrator/aidl/default/include/vibrator-impl/Vibrator.h b/vibrator/aidl/default/include/vibrator-impl/Vibrator.h index 14e7292b43..817ec805fa 100644 --- a/vibrator/aidl/default/include/vibrator-impl/Vibrator.h +++ b/vibrator/aidl/default/include/vibrator-impl/Vibrator.h @@ -32,8 +32,12 @@ class Vibrator : public BnVibrator { const std::shared_ptr& callback, int32_t* _aidl_return) override; ndk::ScopedAStatus getSupportedEffects(std::vector* _aidl_return) override; - ndk::ScopedAStatus setAmplitude(int32_t amplitude) override; + ndk::ScopedAStatus setAmplitude(float amplitude) override; ndk::ScopedAStatus setExternalControl(bool enabled) override; + ndk::ScopedAStatus getCompositionDelayMax(int32_t* maxDelayMs); + ndk::ScopedAStatus getCompositionSizeMax(int32_t* maxSize); + ndk::ScopedAStatus compose(const std::vector& composite, + const std::shared_ptr& callback) override; }; } // namespace vibrator diff --git a/vibrator/aidl/vts/VtsHalVibratorTargetTest.cpp b/vibrator/aidl/vts/VtsHalVibratorTargetTest.cpp index b6aa9e2fd0..5c6120b6f5 100644 --- a/vibrator/aidl/vts/VtsHalVibratorTargetTest.cpp +++ b/vibrator/aidl/vts/VtsHalVibratorTargetTest.cpp @@ -28,6 +28,8 @@ using android::sp; using android::String16; using android::binder::Status; using android::hardware::vibrator::BnVibratorCallback; +using android::hardware::vibrator::CompositeEffect; +using android::hardware::vibrator::CompositePrimitive; using android::hardware::vibrator::Effect; using android::hardware::vibrator::EffectStrength; using android::hardware::vibrator::IVibrator; @@ -55,6 +57,20 @@ const std::vector kInvalidEffectStrengths = { static_cast(static_cast(kEffectStrengths.back()) + 1), }; +// TODO(b/143992652): autogenerate +const std::vector kCompositePrimitives = { + CompositePrimitive::NOOP, CompositePrimitive::CLICK, + CompositePrimitive::THUD, CompositePrimitive::SPIN, + CompositePrimitive::QUICK_RISE, CompositePrimitive::SLOW_RISE, + CompositePrimitive::QUICK_FALL, +}; +// TODO(b/143992652): autogenerate + +const std::vector kInvalidPrimitives = { + static_cast(static_cast(kCompositePrimitives.front()) - 1), + static_cast(static_cast(kCompositePrimitives.back()) + 1), +}; + class CompletionCallback : public BnVibratorCallback { public: CompletionCallback(const std::function& callback) : mCallback(callback) {} @@ -201,11 +217,11 @@ TEST_P(VibratorAidl, InvalidEffectsUnsupported) { TEST_P(VibratorAidl, ChangeVibrationAmplitude) { if (capabilities & IVibrator::CAP_AMPLITUDE_CONTROL) { - EXPECT_TRUE(vibrator->setAmplitude(1).isOk()); + EXPECT_EQ(Status::EX_NONE, vibrator->setAmplitude(0.1f).exceptionCode()); EXPECT_TRUE(vibrator->on(2000, nullptr /*callback*/).isOk()); - EXPECT_TRUE(vibrator->setAmplitude(128).isOk()); + EXPECT_EQ(Status::EX_NONE, vibrator->setAmplitude(0.5f).exceptionCode()); sleep(1); - EXPECT_TRUE(vibrator->setAmplitude(255).isOk()); + EXPECT_EQ(Status::EX_NONE, vibrator->setAmplitude(1.0f).exceptionCode()); sleep(1); } } @@ -214,7 +230,7 @@ TEST_P(VibratorAidl, AmplitudeOutsideRangeFails) { if (capabilities & IVibrator::CAP_AMPLITUDE_CONTROL) { EXPECT_EQ(Status::EX_ILLEGAL_ARGUMENT, vibrator->setAmplitude(-1).exceptionCode()); EXPECT_EQ(Status::EX_ILLEGAL_ARGUMENT, vibrator->setAmplitude(0).exceptionCode()); - EXPECT_EQ(Status::EX_ILLEGAL_ARGUMENT, vibrator->setAmplitude(256).exceptionCode()); + EXPECT_EQ(Status::EX_ILLEGAL_ARGUMENT, vibrator->setAmplitude(1.1).exceptionCode()); } } @@ -240,7 +256,7 @@ TEST_P(VibratorAidl, ExternalAmplitudeControl) { if (capabilities & IVibrator::CAP_EXTERNAL_CONTROL) { EXPECT_TRUE(vibrator->setExternalControl(true).isOk()); - Status amplitudeStatus = vibrator->setAmplitude(128); + Status amplitudeStatus = vibrator->setAmplitude(0.5); if (supportsExternalAmplitudeControl) { EXPECT_TRUE(amplitudeStatus.isOk()); } else { @@ -259,6 +275,102 @@ TEST_P(VibratorAidl, ExternalControlUnsupportedMatchingCapabilities) { } } +TEST_P(VibratorAidl, ComposeValidPrimitives) { + if (capabilities & IVibrator::CAP_COMPOSE_EFFECTS) { + int32_t maxDelay, maxSize; + + EXPECT_EQ(Status::EX_NONE, vibrator->getCompositionDelayMax(&maxDelay).exceptionCode()); + EXPECT_EQ(Status::EX_NONE, vibrator->getCompositionSizeMax(&maxSize).exceptionCode()); + + std::vector composite; + + for (auto primitive : kCompositePrimitives) { + CompositeEffect effect; + + effect.delayMs = std::rand() % (maxDelay + 1); + effect.primitive = primitive; + effect.scale = static_cast(std::rand()) / RAND_MAX ?: 1.0f; + composite.emplace_back(effect); + + if (composite.size() == maxSize) { + EXPECT_EQ(Status::EX_NONE, vibrator->compose(composite, nullptr).exceptionCode()); + composite.clear(); + vibrator->off(); + } + } + + if (composite.size() != 0) { + EXPECT_EQ(Status::EX_NONE, vibrator->compose(composite, nullptr).exceptionCode()); + vibrator->off(); + } + } +} + +TEST_P(VibratorAidl, ComposeUnsupportedPrimitives) { + if (capabilities & IVibrator::CAP_COMPOSE_EFFECTS) { + for (auto primitive : kInvalidPrimitives) { + std::vector composite(1); + + for (auto& effect : composite) { + effect.delayMs = 0; + effect.primitive = primitive; + effect.scale = 1.0f; + } + EXPECT_EQ(Status::EX_UNSUPPORTED_OPERATION, + vibrator->compose(composite, nullptr).exceptionCode()); + vibrator->off(); + } + } +} + +TEST_P(VibratorAidl, CompseDelayBoundary) { + if (capabilities & IVibrator::CAP_COMPOSE_EFFECTS) { + int32_t maxDelay; + + EXPECT_EQ(Status::EX_NONE, vibrator->getCompositionDelayMax(&maxDelay).exceptionCode()); + + std::vector composite(1); + CompositeEffect effect; + + effect.delayMs = 1; + effect.primitive = CompositePrimitive::CLICK; + effect.scale = 1.0f; + + std::fill(composite.begin(), composite.end(), effect); + EXPECT_EQ(Status::EX_NONE, vibrator->compose(composite, nullptr).exceptionCode()); + + effect.delayMs = maxDelay + 1; + + std::fill(composite.begin(), composite.end(), effect); + EXPECT_EQ(Status::EX_ILLEGAL_ARGUMENT, + vibrator->compose(composite, nullptr).exceptionCode()); + vibrator->off(); + } +} + +TEST_P(VibratorAidl, CompseSizeBoundary) { + if (capabilities & IVibrator::CAP_COMPOSE_EFFECTS) { + int32_t maxSize; + + EXPECT_EQ(Status::EX_NONE, vibrator->getCompositionSizeMax(&maxSize).exceptionCode()); + + std::vector composite(maxSize); + CompositeEffect effect; + + effect.delayMs = 1; + effect.primitive = CompositePrimitive::CLICK; + effect.scale = 1.0f; + + std::fill(composite.begin(), composite.end(), effect); + EXPECT_EQ(Status::EX_NONE, vibrator->compose(composite, nullptr).exceptionCode()); + + composite.emplace_back(effect); + EXPECT_EQ(Status::EX_ILLEGAL_ARGUMENT, + vibrator->compose(composite, nullptr).exceptionCode()); + vibrator->off(); + } +} + INSTANTIATE_TEST_SUITE_P(Vibrator, VibratorAidl, testing::ValuesIn(android::getAidlHalInstanceNames(IVibrator::descriptor)), android::PrintInstanceNameToString);