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:
Harpreet \"Eli\" Sangha 2019-10-23 09:25:52 +09:00
parent 43286a882b
commit 7eab6201a9
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.IVibratorCallback;
import android.hardware.vibrator.Effect; import android.hardware.vibrator.Effect;
import android.hardware.vibrator.EffectStrength; import android.hardware.vibrator.EffectStrength;
import android.hardware.vibrator.CompositeEffect;
import android.hardware.vibrator.CompositePrimitive;
@VintfStability @VintfStability
interface IVibrator { interface IVibrator {
@ -42,6 +44,10 @@ interface IVibrator {
* Whether setAmplitude is supported (when external control is enabled) * Whether setAmplitude is supported (when external control is enabled)
*/ */
const int CAP_EXTERNAL_AMPLITUDE_CONTROL = 1 << 4; 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) * Determine capabilities of the vibrator HAL (CAP_* mask)
@ -107,11 +113,10 @@ interface IVibrator {
* CAP_EXTERNAL_AMPLITUDE_CONTROL. * CAP_EXTERNAL_AMPLITUDE_CONTROL.
* *
* @param amplitude The unitless force setting. Note that this number must * @param amplitude The unitless force setting. Note that this number must
* be between 1 and 255, inclusive. If the motor does not * be between 0.0 (exclusive) and 1.0 (inclusive). It must
* have exactly 255 steps, it must do it's best to map it * do it's best to map it onto the number of steps it does have.
* 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. * Enables/disables control override of vibrator to audio.
@ -128,4 +133,36 @@ interface IVibrator {
* @param enabled Whether external control should be enabled or disabled. * @param enabled Whether external control should be enabled or disabled.
*/ */
void setExternalControl(in boolean enabled); 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 hardware {
namespace vibrator { namespace vibrator {
static constexpr int32_t kComposeDelayMaxMs = 1000;
static constexpr int32_t kComposeSizeMax = 256;
ndk::ScopedAStatus Vibrator::getCapabilities(int32_t* _aidl_return) { ndk::ScopedAStatus Vibrator::getCapabilities(int32_t* _aidl_return) {
LOG(INFO) << "Vibrator reporting capabilities"; LOG(INFO) << "Vibrator reporting capabilities";
*_aidl_return = IVibrator::CAP_ON_CALLBACK | IVibrator::CAP_PERFORM_CALLBACK | *_aidl_return = IVibrator::CAP_ON_CALLBACK | IVibrator::CAP_PERFORM_CALLBACK |
IVibrator::CAP_AMPLITUDE_CONTROL | IVibrator::CAP_EXTERNAL_CONTROL | 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(); return ndk::ScopedAStatus::ok();
} }
@ -84,9 +87,9 @@ ndk::ScopedAStatus Vibrator::getSupportedEffects(std::vector<Effect>* _aidl_retu
return ndk::ScopedAStatus::ok(); return ndk::ScopedAStatus::ok();
} }
ndk::ScopedAStatus Vibrator::setAmplitude(int32_t amplitude) { ndk::ScopedAStatus Vibrator::setAmplitude(float amplitude) {
LOG(INFO) << "Vibrator set amplitude: " << 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(AStatus_fromExceptionCode(EX_ILLEGAL_ARGUMENT));
} }
return ndk::ScopedAStatus::ok(); return ndk::ScopedAStatus::ok();
@ -97,6 +100,55 @@ ndk::ScopedAStatus Vibrator::setExternalControl(bool enabled) {
return ndk::ScopedAStatus::ok(); 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 vibrator
} // namespace hardware } // namespace hardware
} // namespace android } // namespace android

View file

@ -32,8 +32,12 @@ class Vibrator : public BnVibrator {
const std::shared_ptr<IVibratorCallback>& callback, const std::shared_ptr<IVibratorCallback>& callback,
int32_t* _aidl_return) override; int32_t* _aidl_return) override;
ndk::ScopedAStatus getSupportedEffects(std::vector<Effect>* _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 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 } // namespace vibrator

View file

@ -28,6 +28,8 @@ using android::sp;
using android::String16; using android::String16;
using android::binder::Status; using android::binder::Status;
using android::hardware::vibrator::BnVibratorCallback; using android::hardware::vibrator::BnVibratorCallback;
using android::hardware::vibrator::CompositeEffect;
using android::hardware::vibrator::CompositePrimitive;
using android::hardware::vibrator::Effect; using android::hardware::vibrator::Effect;
using android::hardware::vibrator::EffectStrength; using android::hardware::vibrator::EffectStrength;
using android::hardware::vibrator::IVibrator; using android::hardware::vibrator::IVibrator;
@ -55,6 +57,20 @@ const std::vector<EffectStrength> kInvalidEffectStrengths = {
static_cast<EffectStrength>(static_cast<int8_t>(kEffectStrengths.back()) + 1), 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 { class CompletionCallback : public BnVibratorCallback {
public: public:
CompletionCallback(const std::function<void()>& callback) : mCallback(callback) {} CompletionCallback(const std::function<void()>& callback) : mCallback(callback) {}
@ -201,11 +217,11 @@ TEST_P(VibratorAidl, InvalidEffectsUnsupported) {
TEST_P(VibratorAidl, ChangeVibrationAmplitude) { TEST_P(VibratorAidl, ChangeVibrationAmplitude) {
if (capabilities & IVibrator::CAP_AMPLITUDE_CONTROL) { 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->on(2000, nullptr /*callback*/).isOk());
EXPECT_TRUE(vibrator->setAmplitude(128).isOk()); EXPECT_EQ(Status::EX_NONE, vibrator->setAmplitude(0.5f).exceptionCode());
sleep(1); sleep(1);
EXPECT_TRUE(vibrator->setAmplitude(255).isOk()); EXPECT_EQ(Status::EX_NONE, vibrator->setAmplitude(1.0f).exceptionCode());
sleep(1); sleep(1);
} }
} }
@ -214,7 +230,7 @@ TEST_P(VibratorAidl, AmplitudeOutsideRangeFails) {
if (capabilities & IVibrator::CAP_AMPLITUDE_CONTROL) { if (capabilities & IVibrator::CAP_AMPLITUDE_CONTROL) {
EXPECT_EQ(Status::EX_ILLEGAL_ARGUMENT, vibrator->setAmplitude(-1).exceptionCode()); 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(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) { if (capabilities & IVibrator::CAP_EXTERNAL_CONTROL) {
EXPECT_TRUE(vibrator->setExternalControl(true).isOk()); EXPECT_TRUE(vibrator->setExternalControl(true).isOk());
Status amplitudeStatus = vibrator->setAmplitude(128); Status amplitudeStatus = vibrator->setAmplitude(0.5);
if (supportsExternalAmplitudeControl) { if (supportsExternalAmplitudeControl) {
EXPECT_TRUE(amplitudeStatus.isOk()); EXPECT_TRUE(amplitudeStatus.isOk());
} else { } 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, INSTANTIATE_TEST_SUITE_P(Vibrator, VibratorAidl,
testing::ValuesIn(android::getAidlHalInstanceNames(IVibrator::descriptor)), testing::ValuesIn(android::getAidlHalInstanceNames(IVibrator::descriptor)),
android::PrintInstanceNameToString); android::PrintInstanceNameToString);