/* * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include using aidl::android::hardware::health::BatteryHealth; using aidl::android::hardware::health::HealthInfo; namespace hardware { namespace google { namespace pixel { namespace health { BatteryDefender::BatteryDefender(const std::string pathWirelessPresent, const std::string pathChargeLevelStart, const std::string pathChargeLevelStop, const int32_t timeToActivateSecs, const int32_t timeToClearTimerSecs, const bool useTypeC) : mPathWirelessPresent(pathWirelessPresent), kPathChargeLevelStart(pathChargeLevelStart), kPathChargeLevelStop(pathChargeLevelStop), kTimeToActivateSecs(timeToActivateSecs), kTimeToClearTimerSecs(timeToClearTimerSecs), kUseTypeC(useTypeC) { mTimePreviousSecs = getTime(); } void BatteryDefender::clearStateData(void) { mHasReachedHighCapacityLevel = false; mTimeActiveSecs = 0; mTimeChargerNotPresentSecs = 0; mTimeChargerPresentSecs = 0; } void BatteryDefender::setWirelessNotSupported(void) { mPathWirelessPresent = PATH_NOT_SUPPORTED; } 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 std::string &path, const bool silent) { std::string buffer; int value = 0; // default if (path == PATH_NOT_SUPPORTED) { return value; } if (!android::base::ReadFileToString(path, &buffer)) { if (silent == false) { LOG(ERROR) << "Failed to read " << path; } } else { removeLineEndings(&buffer); if (!android::base::ParseInt(buffer, &value)) { LOG(ERROR) << "Failed to parse " << path; } } return value; } bool BatteryDefender::writeIntToFile(const std::string &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 std::string &path, const int value, int64_t *previous) { // Some number of seconds delay before repeated writes const bool hasTimeChangedSignificantly = ((value == 0) || (*previous == -1) || (value > (*previous + kWriteDelaySecs)) || (value < (*previous - kWriteDelaySecs))); 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::isTypeCSink(const std::string &path) { std::string buffer; if (!android::base::ReadFileToString(path, &buffer)) { LOG(ERROR) << "Failed to read " << path; } return (buffer.find("[sink]") != std::string::npos); } bool BatteryDefender::isWiredPresent(void) { // Default to USB "present" if type C is not used. if (!kUseTypeC) { return readFileToInt(kPathUSBChargerPresent) != 0; } DIR *dp = opendir(kTypeCPath.c_str()); if (dp == NULL) { LOG(ERROR) << "Failed to read " << kTypeCPath; return false; } struct dirent *ep; std::unordered_map names; while ((ep = readdir(dp))) { if (ep->d_type == DT_LNK) { if (std::string::npos != std::string(ep->d_name).find("-partner")) { std::string portName = std::strtok(ep->d_name, "-"); std::string path = kTypeCPath + portName + "/power_role"; if (isTypeCSink(path)) { closedir(dp); return true; } } } } closedir(dp); return false; } bool BatteryDefender::isDockPresent(void) { return readFileToInt(kPathDOCKChargerPresent, true) != 0; } bool BatteryDefender::isChargePowerAvailable(void) { // USB presence is an indicator of power availability const bool chargerPresentWired = isWiredPresent(); const bool chargerPresentWireless = readFileToInt(mPathWirelessPresent) != 0; const bool chargerPresentDock = isDockPresent(); mIsWiredPresent = chargerPresentWired; mIsWirelessPresent = chargerPresentWireless; mIsDockPresent = chargerPresentDock; return chargerPresentWired || chargerPresentWireless || chargerPresentDock; } 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); } bool BatteryDefender::isDockDefendTrigger(void) { return readFileToInt(kPathDockState, true) == 1; } 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 HealthInfo &health_info) { switch (state) { case STATE_INIT: loadPersistentStorage(); if (health_info.chargerUsbOnline || health_info.chargerAcOnline) { mWasAcOnline = health_info.chargerAcOnline; mWasUsbOnline = health_info.chargerUsbOnline; } break; case STATE_DISABLED: case STATE_DISCONNECTED: clearStateData(); break; case STATE_CONNECTED: { addTimeToChargeTimers(); const int triggerLevel = android::base::GetIntProperty( kPropBatteryDefenderCtrlTriggerSOC, kChargeHighCapacityLevel, 0, 100); if (health_info.batteryLevel >= triggerLevel) { 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); const int bdClear = android::base::GetIntProperty(kPropBatteryDefenderCtrlClear, 0); if (bdClear > 0) { android::base::SetProperty(kPropBatteryDefenderCtrlClear, "0"); nextState = STATE_DISCONNECTED; } /* 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( aidl::android::hardware::health::HealthInfo *health_info) { /** * 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) { health_info->batteryHealth = BatteryHealth::OVERHEAT; } /* Do the same as above when dock-defend triggers */ if (mIsDockDefendTrigger) { health_info->batteryHealth = BatteryHealth::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. * Note; only override "online" if necessary (all "online"s are false). */ if (health_info->chargerUsbOnline == false && health_info->chargerAcOnline == false) { /* Override if the USB is connected and a battery defender is active */ if (mIsWiredPresent && health_info->batteryHealth == BatteryHealth::OVERHEAT) { if (mWasAcOnline) { health_info->chargerAcOnline = true; } if (mWasUsbOnline) { health_info->chargerUsbOnline = true; } } } else { /* One of these booleans will always be true if updated here */ mWasAcOnline = health_info->chargerAcOnline; mWasUsbOnline = health_info->chargerUsbOnline; } /* Do the same as above for wireless adapters */ if (health_info->chargerWirelessOnline == false) { if (mIsWirelessPresent && health_info->batteryHealth == BatteryHealth::OVERHEAT) { health_info->chargerWirelessOnline = true; } } /* Do the same as above for dock adapters */ if (health_info->chargerDockOnline == false) { /* Override if the USB is connected and a battery defender is active */ if (mIsDockPresent && health_info->batteryHealth == BatteryHealth::OVERHEAT) { health_info->chargerDockOnline = true; } } } void BatteryDefender::update(HealthInfo *health_info) { if (!health_info) { 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(); mTimeBetweenUpdateCalls = getDeltaTimeSeconds(&mTimePreviousSecs); mIsDockDefendTrigger = isDockDefendTrigger(); // Run state machine stateMachine_runAction(mCurrentState, *health_info); const state_E nextState = stateMachine_getNextState(mCurrentState); if (nextState != mCurrentState) { stateMachine_firstAction(nextState); } mCurrentState = nextState; // Verify/update battery defender battery properties updateDefenderProperties(health_info); /* May override battery properties */ // Store outputs writeTimeToFile(kPathPersistChargerPresentTime, mTimeChargerPresentSecs, &mTimeChargerPresentSecsPrevious); writeTimeToFile(kPathPersistDefenderActiveTime, mTimeActiveSecs, &mTimeActiveSecsPrevious); writeChargeLevelsToFile(chargeLevelVendorStart, chargeLevelVendorStop); android::base::SetProperty(kPropBatteryDefenderState, kStateStringMap[mCurrentState]); } void BatteryDefender::update(struct android::BatteryProperties *props) { if (!props) { return; } HealthInfo health_info = ToHealthInfo(props); update(&health_info); // Propagate the changes to props props->chargerAcOnline = health_info.chargerAcOnline; props->chargerUsbOnline = health_info.chargerUsbOnline; props->chargerWirelessOnline = health_info.chargerWirelessOnline; props->chargerDockOnline = health_info.chargerDockOnline; props->batteryHealth = static_cast(health_info.batteryHealth); // update() doesn't change other fields. } } // namespace health } // namespace pixel } // namespace google } // namespace hardware