| /* |
| * Copyright (C) 2020 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. |
| */ |
| |
| #define LOG_TAG "BatteryDefender" |
| |
| #include <pixelhealth/BatteryDefender.h> |
| |
| #include <android-base/file.h> |
| #include <android-base/logging.h> |
| #include <android-base/parsebool.h> |
| #include <android-base/parseint.h> |
| #include <android-base/properties.h> |
| |
| #include <time.h> |
| #include <utils/Timers.h> |
| |
| #include <cutils/klog.h> |
| |
| namespace hardware { |
| namespace google { |
| namespace pixel { |
| namespace health { |
| |
| BatteryDefender::BatteryDefender(const char *pathChargeLevelStart, const char *pathChargeLevelStop, |
| const int32_t timeToActivateSecs, |
| const int32_t timeToClearTimerSecs) |
| : kPathChargeLevelStart(pathChargeLevelStart), |
| kPathChargeLevelStop(pathChargeLevelStop), |
| kTimeToActivateSecs(timeToActivateSecs), |
| kTimeToClearTimerSecs(timeToClearTimerSecs) { |
| mTimePreviousSecs = getTime(); |
| } |
| |
| void BatteryDefender::clearStateData(void) { |
| mHasReachedHighCapacityLevel = false; |
| mTimeActiveSecs = 0; |
| mTimeChargerNotPresentSecs = 0; |
| mTimeChargerPresentSecs = 0; |
| } |
| |
| void BatteryDefender::loadPersistentStorage(void) { |
| if (mIsPowerAvailable) { |
| // Load accumulated time from persisted storage |
| mTimeChargerPresentSecs = readFileToInt(kPathPersistChargerPresentTime); |
| mTimeActiveSecs = readFileToInt(kPathPersistDefenderActiveTime); |
| } |
| } |
| |
| int64_t BatteryDefender::getTime(void) { |
| return nanoseconds_to_seconds(systemTime(SYSTEM_TIME_BOOTTIME)); |
| } |
| |
| int64_t BatteryDefender::getDeltaTimeSeconds(int64_t *timeStartSecs) { |
| const int64_t timeCurrentSecs = getTime(); |
| const int64_t timePreviousSecs = *timeStartSecs; |
| *timeStartSecs = timeCurrentSecs; |
| return timeCurrentSecs - timePreviousSecs; |
| } |
| |
| void BatteryDefender::removeLineEndings(std::string *str) { |
| str->erase(std::remove(str->begin(), str->end(), '\n'), str->end()); |
| str->erase(std::remove(str->begin(), str->end(), '\r'), str->end()); |
| } |
| |
| int BatteryDefender::readFileToInt(const char *path) { |
| std::string buffer; |
| int value = 0; // default |
| if (!android::base::ReadFileToString(path, &buffer)) { |
| LOG(ERROR) << "Failed to read " << path; |
| } else { |
| removeLineEndings(&buffer); |
| if (!android::base::ParseInt(buffer.c_str(), &value)) { |
| LOG(ERROR) << "Failed to parse " << path; |
| } |
| } |
| |
| return value; |
| } |
| |
| bool BatteryDefender::writeIntToFile(const char *path, const int value) { |
| bool success = android::base::WriteStringToFile(std::to_string(value), path); |
| if (!success) { |
| LOG(ERROR) << "Failed to write " << path; |
| } |
| |
| return success; |
| } |
| |
| void BatteryDefender::writeTimeToFile(const char *path, const int value, int64_t *previous) { |
| // 30 second delay before repeated writes |
| const bool hasTimeChangedSignificantly = ((value == 0) || (*previous == -1) || |
| (value > *previous + 30) || (value < *previous - 30)); |
| if ((value != *previous) && hasTimeChangedSignificantly) { |
| writeIntToFile(path, value); |
| *previous = value; |
| } |
| } |
| |
| void BatteryDefender::writeChargeLevelsToFile(const int vendorStart, const int vendorStop) { |
| int chargeLevelStart = vendorStart; |
| int chargeLevelStop = vendorStop; |
| if (mCurrentState == STATE_ACTIVE) { |
| const int newDefenderLevelStart = android::base::GetIntProperty( |
| kPropBatteryDefenderCtrlStartSOC, kChargeLevelDefenderStart, 0, 100); |
| const int newDefenderLevelStop = android::base::GetIntProperty( |
| kPropBatteryDefenderCtrlStopSOC, kChargeLevelDefenderStop, 0, 100); |
| const bool overrideLevelsValid = |
| (newDefenderLevelStart <= newDefenderLevelStop) && (newDefenderLevelStop != 0); |
| |
| if (overrideLevelsValid) { |
| chargeLevelStart = newDefenderLevelStart; |
| chargeLevelStop = newDefenderLevelStop; |
| } else { |
| chargeLevelStart = kChargeLevelDefenderStart; |
| chargeLevelStop = kChargeLevelDefenderStop; |
| } |
| } |
| |
| // Disable battery defender effects in charger mode until |
| // b/149598262 is resolved |
| if (android::base::GetProperty(kPropBootmode, "undefined") != "charger") { |
| if (chargeLevelStart != mChargeLevelStartPrevious) { |
| if (writeIntToFile(kPathChargeLevelStart, chargeLevelStart)) { |
| mChargeLevelStartPrevious = chargeLevelStart; |
| } |
| } |
| if (chargeLevelStop != mChargeLevelStopPrevious) { |
| if (writeIntToFile(kPathChargeLevelStop, chargeLevelStop)) { |
| mChargeLevelStopPrevious = chargeLevelStop; |
| } |
| } |
| } |
| } |
| |
| bool BatteryDefender::isChargePowerAvailable(const bool chargerWirelessOnline) { |
| // USB presence is an indicator of power availability |
| const bool chargerPresentWired = readFileToInt(kPathUSBChargerPresent) != 0; |
| mIsUsbPresent = chargerPresentWired; |
| |
| return chargerPresentWired || chargerWirelessOnline; |
| } |
| |
| bool BatteryDefender::isDefaultChargeLevel(const int start, const int stop) { |
| return ((start == kChargeLevelDefaultStart) && (stop == kChargeLevelDefaultStop)); |
| } |
| |
| bool BatteryDefender::isBatteryDefenderDisabled(const int vendorStart, const int vendorStop) { |
| const bool isDefaultVendorChargeLevel = isDefaultChargeLevel(vendorStart, vendorStop); |
| const bool isOverrideDisabled = |
| android::base::GetBoolProperty(kPropBatteryDefenderDisable, false); |
| const bool isCtrlEnabled = |
| android::base::GetBoolProperty(kPropBatteryDefenderCtrlEnable, kDefaultEnable); |
| |
| return isOverrideDisabled || (isDefaultVendorChargeLevel == false) || (isCtrlEnabled == false); |
| } |
| |
| void BatteryDefender::addTimeToChargeTimers(void) { |
| if (mIsPowerAvailable) { |
| if (mHasReachedHighCapacityLevel) { |
| mTimeChargerPresentSecs += mTimeBetweenUpdateCalls; |
| } |
| mTimeChargerNotPresentSecs = 0; |
| } else { |
| mTimeChargerNotPresentSecs += mTimeBetweenUpdateCalls; |
| } |
| } |
| |
| int32_t BatteryDefender::getTimeToActivate(void) { |
| // Use the default constructor value if the modified property is not between 60 and INT_MAX |
| // (seconds) |
| const int32_t timeToActivateOverride = |
| android::base::GetIntProperty(kPropBatteryDefenderThreshold, kTimeToActivateSecs, |
| (int32_t)ONE_MIN_IN_SECONDS, INT32_MAX); |
| |
| const bool overrideActive = timeToActivateOverride != kTimeToActivateSecs; |
| if (overrideActive) { |
| return timeToActivateOverride; |
| } else { |
| // No overrides taken; apply ctrl time to activate... |
| // Note; do not allow less than 1 day trigger time |
| return android::base::GetIntProperty(kPropBatteryDefenderCtrlActivateTime, |
| kTimeToActivateSecs, (int32_t)ONE_DAY_IN_SECONDS, |
| INT32_MAX); |
| } |
| } |
| |
| void BatteryDefender::stateMachine_runAction(const state_E state, |
| const struct android::BatteryProperties *props) { |
| switch (state) { |
| case STATE_INIT: |
| loadPersistentStorage(); |
| if (props->chargerUsbOnline || props->chargerAcOnline) { |
| mWasAcOnline = props->chargerAcOnline; |
| mWasUsbOnline = props->chargerUsbOnline; |
| } |
| break; |
| |
| case STATE_DISABLED: |
| case STATE_DISCONNECTED: |
| clearStateData(); |
| break; |
| |
| case STATE_CONNECTED: |
| addTimeToChargeTimers(); |
| if (props->batteryLevel == kChargeHighCapacityLevel) { |
| mHasReachedHighCapacityLevel = true; |
| } |
| break; |
| |
| case STATE_ACTIVE: |
| addTimeToChargeTimers(); |
| mTimeActiveSecs += mTimeBetweenUpdateCalls; |
| break; |
| |
| default: |
| break; |
| } |
| |
| // Must be loaded after init has set the property |
| mTimeToActivateSecsModified = getTimeToActivate(); |
| } |
| |
| BatteryDefender::state_E BatteryDefender::stateMachine_getNextState(const state_E state) { |
| state_E nextState = state; |
| |
| if (mIsDefenderDisabled) { |
| nextState = STATE_DISABLED; |
| } else { |
| switch (state) { |
| case STATE_INIT: |
| if (mIsPowerAvailable) { |
| if (mTimeChargerPresentSecs > mTimeToActivateSecsModified) { |
| nextState = STATE_ACTIVE; |
| } else { |
| nextState = STATE_CONNECTED; |
| } |
| } else { |
| nextState = STATE_DISCONNECTED; |
| } |
| break; |
| |
| case STATE_DISABLED: |
| nextState = STATE_DISCONNECTED; |
| break; |
| |
| case STATE_DISCONNECTED: |
| if (mIsPowerAvailable) { |
| nextState = STATE_CONNECTED; |
| } |
| break; |
| |
| case STATE_CONNECTED: |
| if (mTimeChargerPresentSecs > mTimeToActivateSecsModified) { |
| nextState = STATE_ACTIVE; |
| } |
| FALLTHROUGH_INTENDED; |
| |
| case STATE_ACTIVE: { |
| const int timeToClear = android::base::GetIntProperty( |
| kPropBatteryDefenderCtrlResumeTime, kTimeToClearTimerSecs, 0, INT32_MAX); |
| |
| /* Check for mIsPowerAvailable in case timeToClear is 0 */ |
| if ((mTimeChargerNotPresentSecs >= timeToClear) && (mIsPowerAvailable == false)) { |
| nextState = STATE_DISCONNECTED; |
| } |
| } break; |
| |
| default: |
| break; |
| } |
| } |
| |
| return nextState; |
| } |
| |
| // This will run once at the rising edge of a new state transition, |
| // in addition to runAction() |
| void BatteryDefender::stateMachine_firstAction(const state_E state) { |
| switch (state) { |
| case STATE_DISABLED: |
| LOG(INFO) << "Disabled!"; |
| FALLTHROUGH_INTENDED; |
| |
| case STATE_DISCONNECTED: |
| clearStateData(); |
| break; |
| |
| case STATE_CONNECTED: |
| // Time already accumulated on state transition implies that there has |
| // already been a full charge cycle (this could happen on boot). |
| if (mTimeChargerPresentSecs > 0) { |
| mHasReachedHighCapacityLevel = true; |
| } |
| break; |
| |
| case STATE_ACTIVE: |
| mHasReachedHighCapacityLevel = true; |
| LOG(INFO) << "Started with " << mTimeChargerPresentSecs |
| << " seconds of power availability!"; |
| break; |
| |
| case STATE_INIT: |
| default: |
| // No actions |
| break; |
| } |
| } |
| |
| void BatteryDefender::updateDefenderProperties(struct android::BatteryProperties *props) { |
| /** |
| * Override the OVERHEAT flag for UI updates to settings. |
| * Also, force AC/USB online if active and still connected to power. |
| */ |
| if (mCurrentState == STATE_ACTIVE) { |
| props->batteryHealth = android::BATTERY_HEALTH_OVERHEAT; |
| } |
| |
| /** |
| * If the kernel is forcing the input current limit to 0, then the online status may |
| * need to be overwritten. Also, setting a charge limit below the current charge level |
| * may disable the adapter. This does not occur for wireless adapters. |
| * Note; only override "online" if necessary (all "online"s are false). |
| */ |
| if (props->chargerUsbOnline == false && props->chargerAcOnline == false) { |
| /* Override if the USB is connected and a battery defender is active */ |
| if (mIsUsbPresent && props->batteryHealth == android::BATTERY_HEALTH_OVERHEAT) { |
| if (mWasAcOnline) { |
| props->chargerAcOnline = true; |
| } |
| if (mWasUsbOnline) { |
| props->chargerUsbOnline = true; |
| } |
| } |
| } else { |
| /* One of these booleans will always be true if updated here */ |
| mWasAcOnline = props->chargerAcOnline; |
| mWasUsbOnline = props->chargerUsbOnline; |
| } |
| } |
| |
| void BatteryDefender::update(struct android::BatteryProperties *props) { |
| if (!props) { |
| return; |
| } |
| |
| // Update module inputs |
| const int chargeLevelVendorStart = |
| android::base::GetIntProperty(kPropChargeLevelVendorStart, kChargeLevelDefaultStart); |
| const int chargeLevelVendorStop = |
| android::base::GetIntProperty(kPropChargeLevelVendorStop, kChargeLevelDefaultStop); |
| mIsDefenderDisabled = isBatteryDefenderDisabled(chargeLevelVendorStart, chargeLevelVendorStop); |
| mIsPowerAvailable = isChargePowerAvailable(props->chargerWirelessOnline); |
| mTimeBetweenUpdateCalls = getDeltaTimeSeconds(&mTimePreviousSecs); |
| |
| // Run state machine |
| stateMachine_runAction(mCurrentState, props); |
| const state_E nextState = stateMachine_getNextState(mCurrentState); |
| if (nextState != mCurrentState) { |
| stateMachine_firstAction(nextState); |
| } |
| mCurrentState = nextState; |
| |
| // Verify/update battery defender battery properties |
| updateDefenderProperties(props); /* May override battery properties */ |
| |
| // Store outputs |
| writeTimeToFile(kPathPersistChargerPresentTime, mTimeChargerPresentSecs, |
| &mTimeChargerPresentSecsPrevious); |
| writeTimeToFile(kPathPersistDefenderActiveTime, mTimeActiveSecs, &mTimeActiveSecsPrevious); |
| writeChargeLevelsToFile(chargeLevelVendorStart, chargeLevelVendorStop); |
| android::base::SetProperty(kPropBatteryDefenderState, stateStringMap[mCurrentState]); |
| } |
| |
| } // namespace health |
| } // namespace pixel |
| } // namespace google |
| } // namespace hardware |