Merge "vibrator: Add Composition APIs"

This commit is contained in:
TreeHugger Robot 2019-11-25 05:04:45 +00:00 committed by Android (Google) Code Review
commit ebd6c752c2
6 changed files with 275 additions and 13 deletions

View 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;
}

View file

@ -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,
}

View file

@ -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);
}

View file

@ -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

View file

@ -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

View file

@ -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);