blob: 37d24e50f43abe9273b6b74f9aef198691053a24 [file] [log] [blame]
/*
* 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