Add Chirp APIs for PWLE sequence creation
Add the necessary Chirp APIs required to allow for piecewise-linear
equation sequencing of haptic waveforms. These APIs will allow
developers to create a fully customizable sequence of haptics waveforms
for playback.
Bug: 162859057
Test: verify pwle sequences can be created and played using idlcli
command. Also verify using atest.
Change-Id: I7fec224b7090e482bbcd1c94a3799ec232cc547f
diff --git a/vibrator/aidl/default/Vibrator.cpp b/vibrator/aidl/default/Vibrator.cpp
index bf61bfe..c6682b3 100644
--- a/vibrator/aidl/default/Vibrator.cpp
+++ b/vibrator/aidl/default/Vibrator.cpp
@@ -26,9 +26,16 @@
static constexpr int32_t kComposeDelayMaxMs = 1000;
static constexpr int32_t kComposeSizeMax = 256;
+static constexpr int32_t kComposePwleSizeMax = 127;
static constexpr float kResonantFrequency = 150.0;
static constexpr float kQFactor = 11.0;
+static constexpr int32_t COMPOSE_PWLE_PRIMITIVE_DURATION_MAX_MS = 16383;
+static constexpr float PWLE_LEVEL_MIN = 0.0;
+static constexpr float PWLE_LEVEL_MAX = 0.98256;
+static constexpr float PWLE_FREQUENCY_RESOLUTION_HZ = 1.0;
+static constexpr float PWLE_FREQUENCY_MIN_HZ = 140.0;
+static constexpr float PWLE_FREQUENCY_MAX_HZ = 160.0;
ndk::ScopedAStatus Vibrator::getCapabilities(int32_t* _aidl_return) {
LOG(INFO) << "Vibrator reporting capabilities";
@@ -36,7 +43,8 @@
IVibrator::CAP_AMPLITUDE_CONTROL | IVibrator::CAP_EXTERNAL_CONTROL |
IVibrator::CAP_EXTERNAL_AMPLITUDE_CONTROL | IVibrator::CAP_COMPOSE_EFFECTS |
IVibrator::CAP_ALWAYS_ON_CONTROL | IVibrator::CAP_GET_RESONANT_FREQUENCY |
- IVibrator::CAP_GET_Q_FACTOR;
+ IVibrator::CAP_GET_Q_FACTOR | IVibrator::CAP_FREQUENCY_CONTROL |
+ IVibrator::CAP_COMPOSE_PWLE_EFFECTS;
return ndk::ScopedAStatus::ok();
}
@@ -215,6 +223,169 @@
return ndk::ScopedAStatus::ok();
}
+ndk::ScopedAStatus Vibrator::getFrequencyResolution(float *freqResolutionHz) {
+ *freqResolutionHz = PWLE_FREQUENCY_RESOLUTION_HZ;
+ return ndk::ScopedAStatus::ok();
+}
+
+ndk::ScopedAStatus Vibrator::getFrequencyMinimum(float *freqMinimumHz) {
+ *freqMinimumHz = PWLE_FREQUENCY_MIN_HZ;
+ return ndk::ScopedAStatus::ok();
+}
+
+ndk::ScopedAStatus Vibrator::getBandwidthAmplitudeMap(std::vector<float> *_aidl_return) {
+ // A valid array should be of size:
+ // (PWLE_FREQUENCY_MAX_HZ - PWLE_FREQUENCY_MIN_HZ) / PWLE_FREQUENCY_RESOLUTION_HZ
+ *_aidl_return = {0.01, 0.02, 0.03, 0.04, 0.05, 0.06, 0.07, 0.08, 0.09, 0.10,
+ 0.11, 0.12, 0.13, 0.14, 0.15, 0.16, 0.17, 0.18, 0.19, 0.20};
+ return ndk::ScopedAStatus::ok();
+}
+
+ndk::ScopedAStatus Vibrator::getPwlePrimitiveDurationMax(int32_t *durationMs) {
+ *durationMs = COMPOSE_PWLE_PRIMITIVE_DURATION_MAX_MS;
+ return ndk::ScopedAStatus::ok();
+}
+
+ndk::ScopedAStatus Vibrator::getPwleCompositionSizeMax(int32_t *maxSize) {
+ *maxSize = kComposePwleSizeMax;
+ return ndk::ScopedAStatus::ok();
+}
+
+ndk::ScopedAStatus Vibrator::getSupportedBraking(std::vector<Braking> *supported) {
+ *supported = {
+ Braking::NONE,
+ Braking::CLAB,
+ };
+ return ndk::ScopedAStatus::ok();
+}
+
+void resetPreviousEndAmplitudeEndFrequency(float &prevEndAmplitude, float &prevEndFrequency) {
+ const float reset = -1.0;
+ prevEndAmplitude = reset;
+ prevEndFrequency = reset;
+}
+
+void incrementIndex(int &index) {
+ index += 1;
+}
+
+void constructActiveDefaults(std::ostringstream &pwleBuilder, const int &segmentIdx) {
+ pwleBuilder << ",C" << segmentIdx << ":1";
+ pwleBuilder << ",B" << segmentIdx << ":0";
+ pwleBuilder << ",AR" << segmentIdx << ":0";
+ pwleBuilder << ",V" << segmentIdx << ":0";
+}
+
+void constructActiveSegment(std::ostringstream &pwleBuilder, const int &segmentIdx, int duration,
+ float amplitude, float frequency) {
+ pwleBuilder << ",T" << segmentIdx << ":" << duration;
+ pwleBuilder << ",L" << segmentIdx << ":" << amplitude;
+ pwleBuilder << ",F" << segmentIdx << ":" << frequency;
+ constructActiveDefaults(pwleBuilder, segmentIdx);
+}
+
+void constructBrakingSegment(std::ostringstream &pwleBuilder, const int &segmentIdx, int duration,
+ Braking brakingType) {
+ pwleBuilder << ",T" << segmentIdx << ":" << duration;
+ pwleBuilder << ",L" << segmentIdx << ":" << 0;
+ pwleBuilder << ",F" << segmentIdx << ":" << 0;
+ pwleBuilder << ",C" << segmentIdx << ":0";
+ pwleBuilder << ",B" << segmentIdx << ":"
+ << static_cast<std::underlying_type<Braking>::type>(brakingType);
+ pwleBuilder << ",AR" << segmentIdx << ":0";
+ pwleBuilder << ",V" << segmentIdx << ":0";
+}
+
+ndk::ScopedAStatus Vibrator::composePwle(const std::vector<PrimitivePwle> &composite,
+ const std::shared_ptr<IVibratorCallback> &callback) {
+ std::ostringstream pwleBuilder;
+ std::string pwleQueue;
+
+ int compositionSizeMax;
+ getPwleCompositionSizeMax(&compositionSizeMax);
+ if (composite.size() <= 0 || composite.size() > compositionSizeMax) {
+ return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
+ }
+
+ float prevEndAmplitude;
+ float prevEndFrequency;
+ resetPreviousEndAmplitudeEndFrequency(prevEndAmplitude, prevEndFrequency);
+
+ int segmentIdx = 0;
+ uint32_t totalDuration = 0;
+
+ pwleBuilder << "S:0,WF:4,RP:0,WT:0";
+
+ for (auto &e : composite) {
+ switch (e.getTag()) {
+ case PrimitivePwle::active: {
+ auto active = e.get<PrimitivePwle::active>();
+ if (active.duration < 0 ||
+ active.duration > COMPOSE_PWLE_PRIMITIVE_DURATION_MAX_MS) {
+ return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
+ }
+ if (active.startAmplitude < PWLE_LEVEL_MIN ||
+ active.startAmplitude > PWLE_LEVEL_MAX ||
+ active.endAmplitude < PWLE_LEVEL_MIN || active.endAmplitude > PWLE_LEVEL_MAX) {
+ return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
+ }
+ if (active.startFrequency < PWLE_FREQUENCY_MIN_HZ ||
+ active.startFrequency > PWLE_FREQUENCY_MAX_HZ ||
+ active.endFrequency < PWLE_FREQUENCY_MIN_HZ ||
+ active.endFrequency > PWLE_FREQUENCY_MAX_HZ) {
+ return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
+ }
+
+ if (!((active.startAmplitude == prevEndAmplitude) &&
+ (active.startFrequency == prevEndFrequency))) {
+ constructActiveSegment(pwleBuilder, segmentIdx, 0, active.startAmplitude,
+ active.startFrequency);
+ incrementIndex(segmentIdx);
+ }
+
+ constructActiveSegment(pwleBuilder, segmentIdx, active.duration,
+ active.endAmplitude, active.endFrequency);
+ incrementIndex(segmentIdx);
+
+ prevEndAmplitude = active.endAmplitude;
+ prevEndFrequency = active.endFrequency;
+ totalDuration += active.duration;
+ break;
+ }
+ case PrimitivePwle::braking: {
+ auto braking = e.get<PrimitivePwle::braking>();
+ if (braking.braking > Braking::CLAB) {
+ return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
+ }
+ if (braking.duration > COMPOSE_PWLE_PRIMITIVE_DURATION_MAX_MS) {
+ return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
+ }
+
+ constructBrakingSegment(pwleBuilder, segmentIdx, 0, braking.braking);
+ incrementIndex(segmentIdx);
+
+ constructBrakingSegment(pwleBuilder, segmentIdx, braking.duration, braking.braking);
+ incrementIndex(segmentIdx);
+
+ resetPreviousEndAmplitudeEndFrequency(prevEndAmplitude, prevEndFrequency);
+ totalDuration += braking.duration;
+ break;
+ }
+ }
+ }
+
+ std::thread([=] {
+ LOG(INFO) << "Starting composePwle on another thread";
+ usleep(totalDuration * 1000);
+ if (callback != nullptr) {
+ LOG(INFO) << "Notifying compose PWLE complete";
+ callback->onComplete();
+ }
+ }).detach();
+
+ return ndk::ScopedAStatus::ok();
+}
+
} // namespace vibrator
} // namespace hardware
} // namespace android