/* * Copyright (C) 2017 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. */ #include "Vibrator.h" #include "utils.h" #include #include #include #include #include #include #include #include #include #include #include #include #include namespace aidl { namespace android { namespace hardware { namespace vibrator { using ::android::NO_ERROR; using ::android::UNEXPECTED_NULL; static constexpr int8_t MAX_RTP_INPUT = 127; static constexpr int8_t MIN_RTP_INPUT = 0; static constexpr char RTP_MODE[] = "rtp"; static constexpr char WAVEFORM_MODE[] = "waveform"; // Use effect #1 in the waveform library for CLICK effect static constexpr char WAVEFORM_CLICK_EFFECT_SEQ[] = "1 0"; // Use effect #2 in the waveform library for TICK effect static constexpr char WAVEFORM_TICK_EFFECT_SEQ[] = "2 0"; // Use effect #3 in the waveform library for DOUBLE_CLICK effect static constexpr char WAVEFORM_DOUBLE_CLICK_EFFECT_SEQ[] = "3 0"; // Use effect #4 in the waveform library for HEAVY_CLICK effect static constexpr char WAVEFORM_HEAVY_CLICK_EFFECT_SEQ[] = "4 0"; // UT team design those target G values static constexpr std::array EFFECT_TARGET_G = {0.19, 0.30, 0.39, 0.66, 0.75}; static constexpr std::array STEADY_TARGET_G = {1.5, 1.145, 0.82}; struct SensorContext { ASensorEventQueue *queue; }; static std::vector sXAxleData; static std::vector sYAxleData; static uint64_t sEndTime = 0; static struct timespec sGetTime; #define MAX_VOLTAGE 3.2 #define FLOAT_EPS 1e-7 #define SENSOR_DATA_NUM 20 // Set sensing period to 2s #define SENSING_PERIOD 2000000000 #define VIBRATION_MOTION_TIME_THRESHOLD 100 #define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0])) int GSensorCallback(__attribute__((unused)) int fd, __attribute__((unused)) int events, void *data) { ASensorEvent event; int event_count = 0; SensorContext *context = reinterpret_cast(data); event_count = ASensorEventQueue_getEvents(context->queue, &event, 1); sXAxleData.push_back(event.data[0]); sYAxleData.push_back(event.data[1]); return 1; } // TODO: b/152305970 int32_t PollGSensor() { int err = NO_ERROR, counter = 0; ASensorManager *sensorManager = nullptr; ASensorRef GSensor; ALooper *looper; struct SensorContext context = {nullptr}; // Get proximity sensor events from the NDK sensorManager = ASensorManager_getInstanceForPackage(""); if (!sensorManager) { ALOGI("Chase %s: Sensor manager is NULL.\n", __FUNCTION__); err = UNEXPECTED_NULL; return 0; } GSensor = ASensorManager_getDefaultSensor(sensorManager, ASENSOR_TYPE_GRAVITY); if (GSensor == nullptr) { ALOGE("%s:Chase Unable to get g sensor\n", __func__); } else { looper = ALooper_forThread(); if (looper == nullptr) { looper = ALooper_prepare(ALOOPER_PREPARE_ALLOW_NON_CALLBACKS); } context.queue = ASensorManager_createEventQueue(sensorManager, looper, 0, GSensorCallback, &context); err = ASensorEventQueue_registerSensor(context.queue, GSensor, 0, 0); if (err != NO_ERROR) { ALOGE("Chase %s: Error %d registering G sensor with event queue.\n", __FUNCTION__, err); return 0; } if (err < 0) { ALOGE("%s:Chase Unable to register for G sensor events\n", __func__); } else { for (counter = 0; counter < SENSOR_DATA_NUM; counter++) { ALooper_pollOnce(5, nullptr, nullptr, nullptr); } } } if (sensorManager != nullptr && context.queue != nullptr) { ASensorEventQueue_disableSensor(context.queue, GSensor); ASensorManager_destroyEventQueue(sensorManager, context.queue); } return 0; } // Temperature protection upper bound 10°C and lower bound 5°C static constexpr int32_t TEMP_UPPER_BOUND = 10000; static constexpr int32_t TEMP_LOWER_BOUND = 5000; // Steady vibration's voltage in lower bound guarantee static uint32_t STEADY_VOLTAGE_LOWER_BOUND = 90; // 1.8 Vpeak static std::uint32_t freqPeriodFormula(std::uint32_t in) { return 1000000000 / (24615 * in); } static std::uint32_t convertLevelsToOdClamp(float voltageLevel, uint32_t lraPeriod) { float odClamp; odClamp = voltageLevel / ((21.32 / 1000.0) * sqrt(1.0 - (static_cast(freqPeriodFormula(lraPeriod)) * 8.0 / 10000.0))); return round(odClamp); } static float targetGToVlevelsUnderLinearEquation(std::array inputCoeffs, float targetG) { // Implement linear equation to get voltage levels, f(x) = ax + b // 0 to 3.2 is our valid output float outPutVal = 0.0f; outPutVal = (targetG - inputCoeffs[1]) / inputCoeffs[0]; if ((outPutVal > FLOAT_EPS) && (outPutVal <= MAX_VOLTAGE)) { return outPutVal; } else { return 0.0f; } } static float targetGToVlevelsUnderCubicEquation(std::array inputCoeffs, float targetG) { // Implement cubic equation to get voltage levels, f(x) = ax^3 + bx^2 + cx + d // 0 to 3.2 is our valid output float AA = 0.0f, BB = 0.0f, CC = 0.0f, Delta = 0.0f; float Y1 = 0.0f, Y2 = 0.0f, K = 0.0f, T = 0.0f, sita = 0.0f; float outPutVal = 0.0f; float oneHalf = 1.0 / 2.0, oneThird = 1.0 / 3.0; float cosSita = 0.0f, sinSitaSqrt3 = 0.0f, sqrtA = 0.0f; AA = inputCoeffs[1] * inputCoeffs[1] - 3.0 * inputCoeffs[0] * inputCoeffs[2]; BB = inputCoeffs[1] * inputCoeffs[2] - 9.0 * inputCoeffs[0] * (inputCoeffs[3] - targetG); CC = inputCoeffs[2] * inputCoeffs[2] - 3.0 * inputCoeffs[1] * (inputCoeffs[3] - targetG); Delta = BB * BB - 4.0 * AA * CC; // There are four discriminants in Shengjin formula. // https://zh.wikipedia.org/wiki/%E4%B8%89%E6%AC%A1%E6%96%B9%E7%A8%8B#%E7%9B%9B%E9%87%91%E5%85%AC%E5%BC%8F%E6%B3%95 if ((fabs(AA) <= FLOAT_EPS) && (fabs(BB) <= FLOAT_EPS)) { // Case 1: A = B = 0 outPutVal = -inputCoeffs[1] / (3 * inputCoeffs[0]); if ((outPutVal > FLOAT_EPS) && (outPutVal <= MAX_VOLTAGE)) { return outPutVal; } return 0.0f; } else if (Delta > FLOAT_EPS) { // Case 2: Delta > 0 Y1 = AA * inputCoeffs[1] + 3.0 * inputCoeffs[0] * (-BB + pow(Delta, oneHalf)) / 2.0; Y2 = AA * inputCoeffs[1] + 3.0 * inputCoeffs[0] * (-BB - pow(Delta, oneHalf)) / 2.0; if ((Y1 < -FLOAT_EPS) && (Y2 > FLOAT_EPS)) { return (-inputCoeffs[1] + pow(-Y1, oneThird) - pow(Y2, oneThird)) / (3.0 * inputCoeffs[0]); } else if ((Y1 > FLOAT_EPS) && (Y2 < -FLOAT_EPS)) { return (-inputCoeffs[1] - pow(Y1, oneThird) + pow(-Y2, oneThird)) / (3.0 * inputCoeffs[0]); } else if ((Y1 < -FLOAT_EPS) && (Y2 < -FLOAT_EPS)) { return (-inputCoeffs[1] + pow(-Y1, oneThird) + pow(-Y2, oneThird)) / (3.0 * inputCoeffs[0]); } else { return (-inputCoeffs[1] - pow(Y1, oneThird) - pow(Y2, oneThird)) / (3.0 * inputCoeffs[0]); } return 0.0f; } else if (Delta < -FLOAT_EPS) { // Case 3: Delta < 0 T = (2 * AA * inputCoeffs[1] - 3 * inputCoeffs[0] * BB) / (2 * AA * sqrt(AA)); sita = acos(T); cosSita = cos(sita / 3); sinSitaSqrt3 = sqrt(3.0) * sin(sita / 3); sqrtA = sqrt(AA); outPutVal = (-inputCoeffs[1] - 2 * sqrtA * cosSita) / (3 * inputCoeffs[0]); if ((outPutVal > FLOAT_EPS) && (outPutVal <= MAX_VOLTAGE)) { return outPutVal; } outPutVal = (-inputCoeffs[1] + sqrtA * (cosSita + sinSitaSqrt3)) / (3 * inputCoeffs[0]); if ((outPutVal > FLOAT_EPS) && (outPutVal <= MAX_VOLTAGE)) { return outPutVal; } outPutVal = (-inputCoeffs[1] + sqrtA * (cosSita - sinSitaSqrt3)) / (3 * inputCoeffs[0]); if ((outPutVal > FLOAT_EPS) && (outPutVal <= MAX_VOLTAGE)) { return outPutVal; } return 0.0f; } else if (Delta <= FLOAT_EPS) { // Case 4: Delta = 0 K = BB / AA; outPutVal = (-inputCoeffs[1] / inputCoeffs[0] + K); if ((outPutVal > FLOAT_EPS) && (outPutVal <= MAX_VOLTAGE)) { return outPutVal; } outPutVal = (-K / 2); if ((outPutVal > FLOAT_EPS) && (outPutVal <= MAX_VOLTAGE)) { return outPutVal; } return 0.0f; } else { // Exception handling return 0.0f; } } static float vLevelsToTargetGUnderCubicEquation(std::array inputCoeffs, float vLevel) { float inputVoltage = 0.0f; inputVoltage = vLevel * MAX_VOLTAGE; return inputCoeffs[0] * pow(inputVoltage, 3) + inputCoeffs[1] * pow(inputVoltage, 2) + inputCoeffs[2] * inputVoltage + inputCoeffs[3]; } static bool motionAwareness() { float avgX = 0.0, avgY = 0.0; uint64_t current_time = 0; clock_gettime(CLOCK_MONOTONIC, &sGetTime); current_time = ((uint64_t)sGetTime.tv_sec * 1000 * 1000 * 1000) + sGetTime.tv_nsec; if ((current_time - sEndTime) > SENSING_PERIOD) { sXAxleData.clear(); sYAxleData.clear(); PollGSensor(); clock_gettime(CLOCK_MONOTONIC, &sGetTime); sEndTime = ((uint64_t)sGetTime.tv_sec * 1000 * 1000 * 1000) + sGetTime.tv_nsec; } avgX = std::accumulate(sXAxleData.begin(), sXAxleData.end(), 0.0) / sXAxleData.size(); avgY = std::accumulate(sYAxleData.begin(), sYAxleData.end(), 0.0) / sYAxleData.size(); if ((avgX > -1.3) && (avgX < 1.3) && (avgY > -0.8) && (avgY < 0.8)) { return false; } else { return true; } } using utils::toUnderlying; Vibrator::Vibrator(std::unique_ptr hwapi, std::unique_ptr hwcal) : mHwApi(std::move(hwapi)), mHwCal(std::move(hwcal)) { std::string autocal; uint32_t lraPeriod = 0, lpTrigSupport = 0; bool hasEffectCoeffs = false, hasSteadyCoeffs = false; std::array effectCoeffs = {0}; std::array steadyCoeffs = {0}; if (!mHwApi->setState(true)) { ALOGE("Failed to set state (%d): %s", errno, strerror(errno)); } if (mHwCal->getAutocal(&autocal)) { mHwApi->setAutocal(autocal); } mHwCal->getLraPeriod(&lraPeriod); mHwCal->getCloseLoopThreshold(&mCloseLoopThreshold); mHwCal->getDynamicConfig(&mDynamicConfig); if (mDynamicConfig) { uint8_t i = 0; float tempVolLevel = 0.0f; float tempAmpMax = 0.0f; uint32_t longFreqencyShift = 0; uint32_t shortVoltageMax = 0, longVoltageMax = 0; uint32_t shape = 0; mHwCal->getLongFrequencyShift(&longFreqencyShift); mHwCal->getShortVoltageMax(&shortVoltageMax); mHwCal->getLongVoltageMax(&longVoltageMax); hasEffectCoeffs = mHwCal->getEffectCoeffs(&effectCoeffs); for (i = 0; i < 5; i++) { if (hasEffectCoeffs) { // Use linear approach to get the target voltage levels if ((effectCoeffs[2] == 0) && (effectCoeffs[3] == 0)) { tempVolLevel = targetGToVlevelsUnderLinearEquation(effectCoeffs, EFFECT_TARGET_G[i]); mEffectTargetOdClamp[i] = convertLevelsToOdClamp(tempVolLevel, lraPeriod); } else { // Use cubic approach to get the target voltage levels tempVolLevel = targetGToVlevelsUnderCubicEquation(effectCoeffs, EFFECT_TARGET_G[i]); mEffectTargetOdClamp[i] = convertLevelsToOdClamp(tempVolLevel, lraPeriod); } } else { mEffectTargetOdClamp[i] = shortVoltageMax; } } // Add a boundary protection for level 5 only, since // some devices might not be able to reach the maximum target G if ((mEffectTargetOdClamp[4] <= 0) || (mEffectTargetOdClamp[4] > shortVoltageMax)) { mEffectTargetOdClamp[4] = shortVoltageMax; } mHwCal->getEffectShape(&shape); mEffectConfig.reset(new VibrationConfig({ .shape = (shape == UINT32_MAX) ? WaveShape::SINE : static_cast(shape), .odClamp = &mEffectTargetOdClamp[0], .olLraPeriod = lraPeriod, })); hasSteadyCoeffs = mHwCal->getSteadyCoeffs(&steadyCoeffs); if (hasSteadyCoeffs) { for (i = 0; i < 3; i++) { // Use cubic approach to get the steady target voltage levels // For steady level 3 voltage which is used for non-motion voltage, we use // interpolation method to calculate the voltage via 20% of MAX // voltage, 60% of MAX voltage and steady level 3 target G if (i == 2) { tempVolLevel = ((STEADY_TARGET_G[2] - vLevelsToTargetGUnderCubicEquation(steadyCoeffs, 0.2)) * 0.4 * MAX_VOLTAGE) / (vLevelsToTargetGUnderCubicEquation(steadyCoeffs, 0.6) - vLevelsToTargetGUnderCubicEquation(steadyCoeffs, 0.2)) + 0.2 * MAX_VOLTAGE; } else { tempVolLevel = targetGToVlevelsUnderCubicEquation(steadyCoeffs, STEADY_TARGET_G[i]); } mSteadyTargetOdClamp[i] = convertLevelsToOdClamp(tempVolLevel, lraPeriod); if ((mSteadyTargetOdClamp[i] <= 0) || (mSteadyTargetOdClamp[i] > longVoltageMax)) { mSteadyTargetOdClamp[i] = longVoltageMax; } } } else { mSteadyTargetOdClamp[0] = mHwCal->getSteadyAmpMax(&tempAmpMax) ? round((STEADY_TARGET_G[0] / tempAmpMax) * longVoltageMax) : longVoltageMax; mSteadyTargetOdClamp[2] = mHwCal->getSteadyAmpMax(&tempAmpMax) ? round((STEADY_TARGET_G[2] / tempAmpMax) * longVoltageMax) : longVoltageMax; } mHwCal->getSteadyShape(&shape); mSteadyConfig.reset(new VibrationConfig({ .shape = (shape == UINT32_MAX) ? WaveShape::SQUARE : static_cast(shape), .odClamp = &mSteadyTargetOdClamp[0], .olLraPeriod = lraPeriod, })); mSteadyOlLraPeriod = lraPeriod; // 1. Change long lra period to frequency // 2. Get frequency': subtract the frequency shift from the frequency // 3. Get final long lra period after put the frequency' to formula mSteadyOlLraPeriodShift = freqPeriodFormula(freqPeriodFormula(lraPeriod) - longFreqencyShift); } else { mHwApi->setOlLraPeriod(lraPeriod); } mHwCal->getClickDuration(&mClickDuration); mHwCal->getTickDuration(&mTickDuration); mHwCal->getDoubleClickDuration(&mDoubleClickDuration); mHwCal->getHeavyClickDuration(&mHeavyClickDuration); // This enables effect #1 from the waveform library to be triggered by SLPI // while the AP is in suspend mode // For default setting, we will enable this feature if that project did not // set the lptrigger config mHwCal->getTriggerEffectSupport(&lpTrigSupport); if (!mHwApi->setLpTriggerEffect(lpTrigSupport)) { ALOGW("Failed to set LP trigger mode (%d): %s", errno, strerror(errno)); } } ndk::ScopedAStatus Vibrator::getCapabilities(int32_t *_aidl_return) { ATRACE_NAME("Vibrator::getCapabilities"); int32_t ret = 0; if (mHwApi->hasRtpInput()) { ret |= IVibrator::CAP_AMPLITUDE_CONTROL; } ret |= IVibrator::CAP_GET_RESONANT_FREQUENCY; *_aidl_return = ret; return ndk::ScopedAStatus::ok(); } ndk::ScopedAStatus Vibrator::on(uint32_t timeoutMs, const char mode[], const std::unique_ptr &config, const int8_t volOffset) { LoopControl loopMode = LoopControl::OPEN; // Open-loop mode is used for short click for over-drive // Close-loop mode is used for long notification for stability if (mode == RTP_MODE && timeoutMs > mCloseLoopThreshold) { loopMode = LoopControl::CLOSE; } mHwApi->setCtrlLoop(toUnderlying(loopMode)); if (!mHwApi->setDuration(timeoutMs)) { ALOGE("Failed to set duration (%d): %s", errno, strerror(errno)); return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE); } mHwApi->setMode(mode); if (config != nullptr) { mHwApi->setLraWaveShape(toUnderlying(config->shape)); mHwApi->setOdClamp(config->odClamp[volOffset]); mHwApi->setOlLraPeriod(config->olLraPeriod); } if (!mHwApi->setActivate(1)) { ALOGE("Failed to activate (%d): %s", errno, strerror(errno)); return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE); } return ndk::ScopedAStatus::ok(); } ndk::ScopedAStatus Vibrator::on(int32_t timeoutMs, const std::shared_ptr &callback) { ATRACE_NAME("Vibrator::on"); if (callback) { return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION); } if (mDynamicConfig) { int temperature = 0; mHwApi->getPATemp(&temperature); if (temperature > TEMP_UPPER_BOUND) { mSteadyConfig->odClamp = &mSteadyTargetOdClamp[0]; mSteadyConfig->olLraPeriod = mSteadyOlLraPeriod; // TODO: b/162346934 This a compromise way to bypass the motion // awareness delay if ((timeoutMs > VIBRATION_MOTION_TIME_THRESHOLD) && (!motionAwareness())) { return on(timeoutMs, RTP_MODE, mSteadyConfig, 2); } } else if (temperature < TEMP_LOWER_BOUND) { mSteadyConfig->odClamp = &STEADY_VOLTAGE_LOWER_BOUND; mSteadyConfig->olLraPeriod = mSteadyOlLraPeriodShift; } } return on(timeoutMs, RTP_MODE, mSteadyConfig, 0); } ndk::ScopedAStatus Vibrator::off() { ATRACE_NAME("Vibrator::off"); if (!mHwApi->setActivate(0)) { ALOGE("Failed to turn vibrator off (%d): %s", errno, strerror(errno)); return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE); } return ndk::ScopedAStatus::ok(); } ndk::ScopedAStatus Vibrator::setAmplitude(float amplitude) { ATRACE_NAME("Vibrator::setAmplitude"); if (amplitude <= 0.0f || amplitude > 1.0f) { return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT); } int32_t rtp_input = std::round(amplitude * (MAX_RTP_INPUT - MIN_RTP_INPUT) + MIN_RTP_INPUT); if (!mHwApi->setRtpInput(rtp_input)) { ALOGE("Failed to set amplitude (%d): %s", errno, strerror(errno)); return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE); } return ndk::ScopedAStatus::ok(); } ndk::ScopedAStatus Vibrator::setExternalControl(bool enabled) { ATRACE_NAME("Vibrator::setExternalControl"); ALOGE("Not support in DRV2624 solution, %d", enabled); return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION); } binder_status_t Vibrator::dump(int fd, const char **args, uint32_t numArgs) { if (fd < 0) { ALOGE("Called debug() with invalid fd."); return STATUS_OK; } (void)args; (void)numArgs; dprintf(fd, "AIDL:\n"); dprintf(fd, " Close Loop Thresh: %" PRIu32 "\n", mCloseLoopThreshold); if (mSteadyConfig) { dprintf(fd, " Steady Shape: %" PRIu32 "\n", mSteadyConfig->shape); dprintf(fd, " Steady OD Clamp: %" PRIu32 " %" PRIu32 " %" PRIu32 "\n", mSteadyConfig->odClamp[0], mSteadyConfig->odClamp[1], mSteadyConfig->odClamp[2]); dprintf(fd, " Steady OL LRA Period: %" PRIu32 "\n", mSteadyConfig->olLraPeriod); } if (mEffectConfig) { dprintf(fd, " Effect Shape: %" PRIu32 "\n", mEffectConfig->shape); dprintf(fd, " Effect OD Clamp: %" PRIu32 " %" PRIu32 " %" PRIu32 " %" PRIu32 " %" PRIu32 "\n", mEffectConfig->odClamp[0], mEffectConfig->odClamp[1], mEffectConfig->odClamp[2], mEffectConfig->odClamp[3], mEffectConfig->odClamp[4]); dprintf(fd, " Effect OL LRA Period: %" PRIu32 "\n", mEffectConfig->olLraPeriod); } dprintf(fd, " Click Duration: %" PRIu32 "\n", mClickDuration); dprintf(fd, " Tick Duration: %" PRIu32 "\n", mTickDuration); dprintf(fd, " Double Click Duration: %" PRIu32 "\n", mDoubleClickDuration); dprintf(fd, " Heavy Click Duration: %" PRIu32 "\n", mHeavyClickDuration); dprintf(fd, "\n"); mHwApi->debug(fd); dprintf(fd, "\n"); mHwCal->debug(fd); fsync(fd); return STATUS_OK; } ndk::ScopedAStatus Vibrator::getSupportedEffects(std::vector *_aidl_return) { *_aidl_return = {Effect::TEXTURE_TICK, Effect::TICK, Effect::CLICK, Effect::HEAVY_CLICK, Effect::DOUBLE_CLICK}; return ndk::ScopedAStatus::ok(); } ndk::ScopedAStatus Vibrator::perform(Effect effect, EffectStrength strength, const std::shared_ptr &callback, int32_t *_aidl_return) { ATRACE_NAME("Vibrator::perform"); ndk::ScopedAStatus status; if (callback) { status = ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION); } else { status = performEffect(effect, strength, _aidl_return); } return status; } ndk::ScopedAStatus Vibrator::performEffect(Effect effect, EffectStrength strength, int32_t *outTimeMs) { ndk::ScopedAStatus status; uint32_t timeMS; int8_t volOffset; switch (strength) { case EffectStrength::LIGHT: volOffset = 0; break; case EffectStrength::MEDIUM: volOffset = 1; break; case EffectStrength::STRONG: volOffset = 1; break; default: return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION); break; } switch (effect) { case Effect::TEXTURE_TICK: mHwApi->setSequencer(WAVEFORM_TICK_EFFECT_SEQ); timeMS = mTickDuration; volOffset = TEXTURE_TICK; break; case Effect::CLICK: mHwApi->setSequencer(WAVEFORM_CLICK_EFFECT_SEQ); timeMS = mClickDuration; volOffset += CLICK; break; case Effect::DOUBLE_CLICK: mHwApi->setSequencer(WAVEFORM_DOUBLE_CLICK_EFFECT_SEQ); timeMS = mDoubleClickDuration; volOffset += CLICK; break; case Effect::TICK: mHwApi->setSequencer(WAVEFORM_TICK_EFFECT_SEQ); timeMS = mTickDuration; volOffset += TICK; break; case Effect::HEAVY_CLICK: mHwApi->setSequencer(WAVEFORM_HEAVY_CLICK_EFFECT_SEQ); timeMS = mHeavyClickDuration; volOffset += HEAVY_CLICK; break; default: return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION); } status = on(timeMS, WAVEFORM_MODE, mEffectConfig, volOffset); if (!status.isOk()) { return status; } *outTimeMs = timeMS; return ndk::ScopedAStatus::ok(); } ndk::ScopedAStatus Vibrator::getSupportedAlwaysOnEffects(std::vector * /*_aidl_return*/) { return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION); } ndk::ScopedAStatus Vibrator::alwaysOnEnable(int32_t /*id*/, Effect /*effect*/, EffectStrength /*strength*/) { return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION); } ndk::ScopedAStatus Vibrator::alwaysOnDisable(int32_t /*id*/) { return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION); } ndk::ScopedAStatus Vibrator::getCompositionDelayMax(int32_t * /*maxDelayMs*/) { return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION); } ndk::ScopedAStatus Vibrator::getCompositionSizeMax(int32_t * /*maxSize*/) { return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION); } ndk::ScopedAStatus Vibrator::getSupportedPrimitives(std::vector * /*supported*/) { return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION); } ndk::ScopedAStatus Vibrator::getPrimitiveDuration(CompositePrimitive /*primitive*/, int32_t * /*durationMs*/) { return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION); } ndk::ScopedAStatus Vibrator::compose(const std::vector & /*composite*/, const std::shared_ptr & /*callback*/) { return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION); } static float freqPeriodFormulaFloat(std::uint32_t in) { return static_cast(1000000000) / static_cast(24615 * in); } ndk::ScopedAStatus Vibrator::getResonantFrequency(float *resonantFreqHz) { uint32_t lraPeriod; if(!mHwCal->getLraPeriod(&lraPeriod)) { ALOGE("Failed to get resonant frequency (%d): %s", errno, strerror(errno)); return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE); } *resonantFreqHz = freqPeriodFormulaFloat(lraPeriod); return ndk::ScopedAStatus::ok(); } ndk::ScopedAStatus Vibrator::getQFactor(float * /*qFactor*/) { return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION); } } // namespace vibrator } // namespace hardware } // namespace android } // namespace aidl