| /* |
| * 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 <android/looper.h> |
| #include <android/sensor.h> |
| #include <cutils/properties.h> |
| #include <hardware/hardware.h> |
| #include <hardware/vibrator.h> |
| #include <log/log.h> |
| #include <utils/Errors.h> |
| #include <utils/Trace.h> |
| |
| #include <cinttypes> |
| #include <cmath> |
| #include <fstream> |
| #include <iostream> |
| #include <numeric> |
| |
| 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<float, 5> EFFECT_TARGET_G = {0.19, 0.30, 0.39, 0.66, 0.75}; |
| static constexpr std::array<float, 3> STEADY_TARGET_G = {1.5, 1.145, 0.82}; |
| |
| struct SensorContext { |
| ASensorEventQueue *queue; |
| }; |
| static std::vector<float> sXAxleData; |
| static std::vector<float> 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<SensorContext *>(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<float>(freqPeriodFormula(lraPeriod)) * 8.0 / 10000.0))); |
| |
| return round(odClamp); |
| } |
| |
| static float targetGToVlevelsUnderLinearEquation(std::array<float, 4> 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<float, 4> 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<float, 4> 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> hwapi, std::unique_ptr<HwCal> 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<float, 4> effectCoeffs = {0}; |
| std::array<float, 4> 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<WaveShape>(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<WaveShape>(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<VibrationConfig> &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<IVibratorCallback> &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<Effect> *_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<IVibratorCallback> &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<Effect> * /*_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<CompositePrimitive> * /*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<CompositeEffect> & /*composite*/, |
| const std::shared_ptr<IVibratorCallback> & /*callback*/) { |
| return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION); |
| } |
| |
| static float freqPeriodFormulaFloat(std::uint32_t in) { |
| return static_cast<float>(1000000000) / static_cast<float>(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); |
| } |
| |
| ndk::ScopedAStatus Vibrator::getFrequencyResolution(float * /*freqResolutionHz*/) { |
| return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION); |
| } |
| |
| ndk::ScopedAStatus Vibrator::getFrequencyMinimum(float * /*freqMinimumHz*/) { |
| return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION); |
| } |
| |
| ndk::ScopedAStatus Vibrator::getBandwidthAmplitudeMap(std::vector<float> * /*_aidl_return*/) { |
| return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION); |
| } |
| |
| ndk::ScopedAStatus Vibrator::getPwlePrimitiveDurationMax(int32_t * /*durationMs*/) { |
| return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION); |
| } |
| |
| ndk::ScopedAStatus Vibrator::getPwleCompositionSizeMax(int32_t * /*maxSize*/) { |
| return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION); |
| } |
| |
| ndk::ScopedAStatus Vibrator::getSupportedBraking(std::vector<Braking> * /*supported*/) { |
| return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION); |
| } |
| |
| ndk::ScopedAStatus Vibrator::composePwle(const std::vector<PrimitivePwle> & /*composite*/, |
| const std::shared_ptr<IVibratorCallback> & /*callback*/) { |
| return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION); |
| } |
| |
| } // namespace vibrator |
| } // namespace hardware |
| } // namespace android |
| } // namespace aidl |