blob: e60843fbeafd8660ad7472fb692faea37b2ecfbc [file] [log] [blame]
// Copyright (c) 2013 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "power_manager/powerd/policy/ambient_light_handler.h"
#include <cmath>
#include <limits>
#include <base/strings/string_number_conversions.h>
#include <base/strings/string_split.h>
#include <base/strings/string_util.h>
#include "power_manager/powerd/system/ambient_light_sensor_interface.h"
namespace power_manager {
namespace policy {
namespace {
// Number of light sensor responses required to overcome temporal hysteresis.
const int kHysteresisThreshold = 2;
} // namespace
// static
BacklightBrightnessChange_Cause AmbientLightHandler::ToProtobufCause(
BrightnessChangeCause als_cause) {
switch (als_cause) {
case BrightnessChangeCause::AMBIENT_LIGHT:
return BacklightBrightnessChange_Cause_AMBIENT_LIGHT_CHANGED;
case BrightnessChangeCause::EXTERNAL_POWER_CONNECTED:
return BacklightBrightnessChange_Cause_EXTERNAL_POWER_CONNECTED;
case BrightnessChangeCause::EXTERNAL_POWER_DISCONNECTED:
return BacklightBrightnessChange_Cause_EXTERNAL_POWER_DISCONNECTED;
}
NOTREACHED() << "Invalid cause " << static_cast<int>(als_cause);
return BacklightBrightnessChange_Cause_AMBIENT_LIGHT_CHANGED;
}
constexpr size_t AmbientLightHandler::kNumRecentReadingsToLog;
AmbientLightHandler::AmbientLightHandler(
system::AmbientLightSensorInterface* sensor, Delegate* delegate)
: sensor_(sensor),
delegate_(delegate),
power_source_(PowerSource::AC),
smoothed_lux_at_last_adjustment_(0),
smoothed_lux_(0),
smoothing_constant_(1.0),
hysteresis_state_(HysteresisState::IMMEDIATE),
hysteresis_count_(0),
step_index_(0),
sent_initial_adjustment_(false) {
DCHECK(sensor_);
DCHECK(delegate_);
recent_lux_readings_.reserve(kNumRecentReadingsToLog);
sensor_->AddObserver(this);
}
AmbientLightHandler::~AmbientLightHandler() {
sensor_->RemoveObserver(this);
}
void AmbientLightHandler::Init(const std::string& steps_pref_value,
double initial_brightness_percent,
double smoothing_constant) {
std::vector<std::string> lines = base::SplitString(
steps_pref_value, "\n", base::KEEP_WHITESPACE, base::SPLIT_WANT_ALL);
for (std::vector<std::string>::iterator iter = lines.begin();
iter != lines.end(); ++iter) {
std::vector<std::string> segments = base::SplitString(
*iter, " ", base::KEEP_WHITESPACE, base::SPLIT_WANT_ALL);
BrightnessStep new_step;
if (segments.size() == 3 &&
base::StringToDouble(segments[0], &new_step.ac_target_percent) &&
base::StringToInt(segments[1], &new_step.decrease_lux_threshold) &&
base::StringToInt(segments[2], &new_step.increase_lux_threshold)) {
new_step.battery_target_percent = new_step.ac_target_percent;
} else if (segments.size() == 4 &&
base::StringToDouble(segments[0], &new_step.ac_target_percent) &&
base::StringToDouble(segments[1],
&new_step.battery_target_percent) &&
base::StringToInt(segments[2],
&new_step.decrease_lux_threshold) &&
base::StringToInt(segments[3],
&new_step.increase_lux_threshold)) {
// Okay, we've read all the fields.
} else {
LOG(FATAL) << "Steps pref has invalid line \"" << *iter << "\"";
}
steps_.push_back(new_step);
}
// The bottom and top steps should have infinite ranges to ensure that we
// don't fall off either end.
CHECK(!steps_.empty()) << "No brightness steps defined in pref";
CHECK_EQ(steps_.front().decrease_lux_threshold, -1);
CHECK_EQ(steps_.back().increase_lux_threshold, -1);
// Start at the step nearest to the initial backlight level.
double percent_delta = std::numeric_limits<double>::max();
for (size_t i = 0; i < steps_.size(); i++) {
double temp_delta =
fabs(initial_brightness_percent - steps_[i].ac_target_percent);
if (temp_delta < percent_delta) {
percent_delta = temp_delta;
step_index_ = i;
}
}
CHECK_LT(step_index_, steps_.size());
// Create a synthetic lux value that is in line with |step_index_|.
// If one or both of the thresholds are unbounded, just do the best we
// can.
if (steps_[step_index_].decrease_lux_threshold >= 0 &&
steps_[step_index_].increase_lux_threshold >= 0) {
smoothed_lux_at_last_adjustment_ =
steps_[step_index_].decrease_lux_threshold +
(steps_[step_index_].increase_lux_threshold -
steps_[step_index_].decrease_lux_threshold) /
2;
} else if (steps_[step_index_].decrease_lux_threshold >= 0) {
smoothed_lux_at_last_adjustment_ =
steps_[step_index_].decrease_lux_threshold;
} else if (steps_[step_index_].increase_lux_threshold >= 0) {
smoothed_lux_at_last_adjustment_ =
steps_[step_index_].increase_lux_threshold;
} else {
smoothed_lux_at_last_adjustment_ = 0;
}
CHECK_GT(smoothing_constant, 0.0);
CHECK_LE(smoothing_constant, 1.0);
smoothing_constant_ = smoothing_constant;
}
void AmbientLightHandler::HandlePowerSourceChange(PowerSource source) {
if (source == power_source_)
return;
double old_percent = GetTargetPercent();
power_source_ = source;
double new_percent = GetTargetPercent();
if (new_percent != old_percent && sent_initial_adjustment_) {
LOG(INFO) << "Going from " << old_percent << "% to " << new_percent
<< "% for power source change (" << name_ << ")";
delegate_->SetBrightnessPercentForAmbientLight(
new_percent, source == PowerSource::AC
? BrightnessChangeCause::EXTERNAL_POWER_CONNECTED
: BrightnessChangeCause::EXTERNAL_POWER_DISCONNECTED);
}
}
void AmbientLightHandler::HandleResume() {
hysteresis_state_ = HysteresisState::RESUMING;
}
std::string AmbientLightHandler::GetRecentReadingsString() const {
std::string str;
for (int i = 0; i < recent_lux_readings_.size(); ++i) {
const int index =
(recent_lux_start_index_ - i - 1 + recent_lux_readings_.size()) %
recent_lux_readings_.size();
str += (i ? " " : "") + std::to_string(recent_lux_readings_[index]);
}
return str;
}
void AmbientLightHandler::OnAmbientLightUpdated(
system::AmbientLightSensorInterface* sensor) {
DCHECK_EQ(sensor, sensor_);
// Discard first reading after resume as it is probably cached value.
if (hysteresis_state_ == HysteresisState::RESUMING) {
hysteresis_state_ = HysteresisState::IMMEDIATE;
return;
}
const int raw_lux = sensor_->GetAmbientLightLux();
if (raw_lux < 0) {
LOG(WARNING) << "Sensor doesn't have valid value";
return;
}
// Currently we notify on every color temperature change.
if (sensor_->IsColorSensor()) {
const int color_temperature = sensor_->GetColorTemperature();
if (color_temperature >= 0)
delegate_->OnColorTemperatureChanged(color_temperature);
}
if (recent_lux_readings_.size() < kNumRecentReadingsToLog) {
recent_lux_readings_.push_back(raw_lux);
} else {
// Overwrite the oldest value with the new reading.
recent_lux_readings_[recent_lux_start_index_] = raw_lux;
recent_lux_start_index_ =
(recent_lux_start_index_ + 1) % recent_lux_readings_.size();
}
UpdateSmoothedLux(raw_lux);
const int new_lux = lround(smoothed_lux_);
if (hysteresis_state_ != HysteresisState::IMMEDIATE &&
new_lux == smoothed_lux_at_last_adjustment_) {
hysteresis_state_ = HysteresisState::STABLE;
return;
}
int new_step_index = step_index_;
int num_steps = steps_.size();
if (new_lux > smoothed_lux_at_last_adjustment_) {
if (hysteresis_state_ != HysteresisState::IMMEDIATE &&
hysteresis_state_ != HysteresisState::INCREASING) {
VLOG(1) << "ALS transitioned to brightness increasing (" << name_ << ")";
hysteresis_state_ = HysteresisState::INCREASING;
hysteresis_count_ = 0;
}
for (; new_step_index < num_steps; new_step_index++) {
if (new_lux < steps_[new_step_index].increase_lux_threshold ||
steps_[new_step_index].increase_lux_threshold == -1)
break;
}
} else if (new_lux < smoothed_lux_at_last_adjustment_) {
if (hysteresis_state_ != HysteresisState::IMMEDIATE &&
hysteresis_state_ != HysteresisState::DECREASING) {
VLOG(1) << "ALS transitioned to brightness decreasing (" << name_ << ")";
hysteresis_state_ = HysteresisState::DECREASING;
hysteresis_count_ = 0;
}
for (; new_step_index >= 0; new_step_index--) {
if (new_lux > steps_[new_step_index].decrease_lux_threshold ||
steps_[new_step_index].decrease_lux_threshold == -1)
break;
}
}
CHECK_GE(new_step_index, 0);
CHECK_LT(new_step_index, num_steps);
if (hysteresis_state_ == HysteresisState::IMMEDIATE) {
step_index_ = new_step_index;
double target_percent = GetTargetPercent();
LOG(INFO) << "Immediately going to " << target_percent << "% (step "
<< step_index_ << ") for lux " << new_lux << " (" << name_ << ")";
smoothed_lux_at_last_adjustment_ = new_lux;
hysteresis_state_ = HysteresisState::STABLE;
hysteresis_count_ = 0;
delegate_->SetBrightnessPercentForAmbientLight(
target_percent, BrightnessChangeCause::AMBIENT_LIGHT);
sent_initial_adjustment_ = true;
return;
}
if (static_cast<int>(step_index_) == new_step_index)
return;
hysteresis_count_++;
VLOG(1) << "Incremented hysteresis count to " << hysteresis_count_
<< " (lux went from " << smoothed_lux_at_last_adjustment_ << " to "
<< new_lux << ") (" << name_ << ")";
if (hysteresis_count_ >= kHysteresisThreshold) {
step_index_ = new_step_index;
double target_percent = GetTargetPercent();
// Log the backlight brightness level that we're suggesting. Note that the
// delegate may choose to ignore this suggestion for some other reason
// (system is shutting down, user has manually requested a different level,
// etc.).
LOG(INFO) << "Transitioning " << name_ << " to " << target_percent
<< "% (step " << step_index_ << ") for lux " << new_lux << " ["
<< GetRecentReadingsString() << " ...]";
smoothed_lux_at_last_adjustment_ = new_lux;
hysteresis_count_ = 1;
delegate_->SetBrightnessPercentForAmbientLight(
target_percent, BrightnessChangeCause::AMBIENT_LIGHT);
sent_initial_adjustment_ = true;
}
}
double AmbientLightHandler::GetTargetPercent() const {
CHECK_LT(step_index_, steps_.size());
return power_source_ == PowerSource::AC
? steps_[step_index_].ac_target_percent
: steps_[step_index_].battery_target_percent;
}
void AmbientLightHandler::UpdateSmoothedLux(int raw_lux) {
// For the first sensor reading, use raw lux value directly without smoothing.
if (hysteresis_state_ == HysteresisState::IMMEDIATE) {
smoothed_lux_ = raw_lux;
} else {
smoothed_lux_ = smoothing_constant_ * raw_lux +
(1 - smoothing_constant_) * smoothed_lux_;
}
}
} // namespace policy
} // namespace power_manager