vibrator: Add Composition APIs
Bug: 139762802 Test: Manual Invocation via 'idlcli' Change-Id: Ibc938d08f186039681d523784b90f4172a52af51 Signed-off-by: Harpreet \"Eli\" Sangha <eliptus@google.com>
This commit is contained in:
parent
43286a882b
commit
7eab6201a9
6 changed files with 275 additions and 13 deletions
28
vibrator/aidl/android/hardware/vibrator/CompositeEffect.aidl
Normal file
28
vibrator/aidl/android/hardware/vibrator/CompositeEffect.aidl
Normal file
|
@ -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;
|
||||
}
|
|
@ -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,
|
||||
}
|
|
@ -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);
|
||||
|
||||
}
|
||||
|
|
|
@ -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<Effect>* _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<CompositeEffect>& composite,
|
||||
const std::shared_ptr<IVibratorCallback>& 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<int>(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
|
||||
|
|
|
@ -32,8 +32,12 @@ class Vibrator : public BnVibrator {
|
|||
const std::shared_ptr<IVibratorCallback>& callback,
|
||||
int32_t* _aidl_return) override;
|
||||
ndk::ScopedAStatus getSupportedEffects(std::vector<Effect>* _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<CompositeEffect>& composite,
|
||||
const std::shared_ptr<IVibratorCallback>& callback) override;
|
||||
};
|
||||
|
||||
} // namespace vibrator
|
||||
|
|
|
@ -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<EffectStrength> kInvalidEffectStrengths = {
|
|||
static_cast<EffectStrength>(static_cast<int8_t>(kEffectStrengths.back()) + 1),
|
||||
};
|
||||
|
||||
// TODO(b/143992652): autogenerate
|
||||
const std::vector<CompositePrimitive> 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<CompositePrimitive> kInvalidPrimitives = {
|
||||
static_cast<CompositePrimitive>(static_cast<int32_t>(kCompositePrimitives.front()) - 1),
|
||||
static_cast<CompositePrimitive>(static_cast<int32_t>(kCompositePrimitives.back()) + 1),
|
||||
};
|
||||
|
||||
class CompletionCallback : public BnVibratorCallback {
|
||||
public:
|
||||
CompletionCallback(const std::function<void()>& 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<CompositeEffect> composite;
|
||||
|
||||
for (auto primitive : kCompositePrimitives) {
|
||||
CompositeEffect effect;
|
||||
|
||||
effect.delayMs = std::rand() % (maxDelay + 1);
|
||||
effect.primitive = primitive;
|
||||
effect.scale = static_cast<float>(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<CompositeEffect> 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<CompositeEffect> 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<CompositeEffect> 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);
|
||||
|
|
Loading…
Reference in a new issue