blob: ed6c083bdb86ad637b695b2e5cfb11be3ffb4fa8 [file] [log] [blame]
// Copyright (c) 2012 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/keyboard_backlight_controller.h"
#include <algorithm>
#include <cmath>
#include <cstdlib>
#include <functional>
#include <limits>
#include <string>
#include <utility>
#include <vector>
#include <base/bind.h>
#include <base/strings/stringprintf.h>
#include <base/strings/string_number_conversions.h>
#include <base/strings/string_split.h>
#include <base/strings/string_util.h>
#include <chromeos/dbus/service_constants.h>
#include "power_manager/common/clock.h"
#include "power_manager/common/prefs.h"
#include "power_manager/common/util.h"
#include "power_manager/powerd/system/backlight_interface.h"
namespace power_manager {
namespace policy {
namespace {
// This is how long after a video playing message is received we should wait
// until reverting to the not playing state. If another message is received in
// this interval the timeout is reset. The browser should be sending these
// messages ~5 seconds when video is playing.
const int64_t kVideoTimeoutIntervalMs = 7000;
// Maximum valid value for scaled percentages.
const double kMaxPercent = 100.0;
// Minimum valid value for scaled percentages.
const double kMinPercent = 0.0;
// Second minimum step in scaled percentages.
const double kMinVisiblePercent = 10.0;
// Returns the total duration for |style|.
base::TimeDelta GetTransitionDuration(
BacklightController::Transition transition) {
switch (transition) {
case BacklightController::Transition::INSTANT:
return base::TimeDelta();
case BacklightController::Transition::FAST:
return base::TimeDelta::FromMilliseconds(kFastBacklightTransitionMs);
case BacklightController::Transition::SLOW:
return base::TimeDelta::FromMilliseconds(kSlowBacklightTransitionMs);
}
NOTREACHED() << "Unhandled transition style " << static_cast<int>(transition);
return base::TimeDelta();
}
} // namespace
KeyboardBacklightController::TestApi::TestApi(
KeyboardBacklightController* controller)
: controller_(controller) {}
KeyboardBacklightController::TestApi::~TestApi() {}
bool KeyboardBacklightController::TestApi::TriggerTurnOffTimeout() {
if (!controller_->turn_off_timer_.IsRunning())
return false;
controller_->turn_off_timer_.Stop();
controller_->UpdateState(Transition::SLOW,
BacklightBrightnessChange_Cause_OTHER);
return true;
}
bool KeyboardBacklightController::TestApi::TriggerVideoTimeout() {
if (!controller_->video_timer_.IsRunning())
return false;
controller_->video_timer_.Stop();
controller_->HandleVideoTimeout();
return true;
}
const double KeyboardBacklightController::kDimPercent = 10.0;
KeyboardBacklightController::KeyboardBacklightController()
: clock_(std::make_unique<Clock>()), weak_ptr_factory_(this) {}
KeyboardBacklightController::~KeyboardBacklightController() {
if (display_backlight_controller_)
display_backlight_controller_->RemoveObserver(this);
if (backlight_)
backlight_->RemoveObserver(this);
}
void KeyboardBacklightController::Init(
system::BacklightInterface* backlight,
PrefsInterface* prefs,
system::AmbientLightSensorInterface* sensor,
system::DBusWrapperInterface* dbus_wrapper,
BacklightController* display_backlight_controller,
LidState initial_lid_state,
TabletMode initial_tablet_mode) {
backlight_ = backlight;
backlight_->AddObserver(this);
prefs_ = prefs;
lid_state_ = initial_lid_state;
tablet_mode_ = initial_tablet_mode;
dbus_wrapper_ = dbus_wrapper;
RegisterIncreaseBrightnessHandler(
dbus_wrapper_, kIncreaseKeyboardBrightnessMethod,
base::Bind(&KeyboardBacklightController::HandleIncreaseBrightnessRequest,
weak_ptr_factory_.GetWeakPtr()));
RegisterDecreaseBrightnessHandler(
dbus_wrapper_, kDecreaseKeyboardBrightnessMethod,
base::Bind(&KeyboardBacklightController::HandleDecreaseBrightnessRequest,
weak_ptr_factory_.GetWeakPtr()));
RegisterGetBrightnessHandler(
dbus_wrapper_, kGetKeyboardBrightnessPercentMethod,
base::Bind(&KeyboardBacklightController::HandleGetBrightnessRequest,
weak_ptr_factory_.GetWeakPtr()));
display_backlight_controller_ = display_backlight_controller;
if (display_backlight_controller_)
display_backlight_controller_->AddObserver(this);
if (sensor) {
ambient_light_handler_.reset(new AmbientLightHandler(sensor, this));
ambient_light_handler_->set_name("keyboard");
}
prefs_->GetBool(kDetectHoverPref, &supports_hover_);
prefs_->GetBool(kKeyboardBacklightTurnOnForUserActivityPref,
&turn_on_for_user_activity_);
int64_t delay_ms = 0;
CHECK(prefs->GetInt64(kKeyboardBacklightKeepOnMsPref, &delay_ms));
keep_on_delay_ = base::TimeDelta::FromMilliseconds(delay_ms);
CHECK(prefs->GetInt64(kKeyboardBacklightKeepOnDuringVideoMsPref, &delay_ms));
keep_on_during_video_delay_ = base::TimeDelta::FromMilliseconds(delay_ms);
// Read the user-settable brightness steps (one per line).
std::string input_str;
if (!prefs_->GetString(kKeyboardBacklightUserStepsPref, &input_str))
LOG(FATAL) << "Failed to read pref " << kKeyboardBacklightUserStepsPref;
std::vector<std::string> lines = base::SplitString(
input_str, "\n", base::KEEP_WHITESPACE, base::SPLIT_WANT_ALL);
for (std::vector<std::string>::iterator iter = lines.begin();
iter != lines.end(); ++iter) {
double new_step = 0.0;
if (!base::StringToDouble(*iter, &new_step))
LOG(FATAL) << "Invalid line in pref " << kKeyboardBacklightUserStepsPref
<< ": \"" << *iter << "\"";
user_steps_.push_back(new_step);
}
// Validate raw percentages in |user_steps_|.
std::string err_msg;
CHECK(ValidateUserSteps(&err_msg)) << err_msg;
// Initialize |min_raw_percent_|, |min_visible_raw_percent| and
// |max_raw_percent_| and calculate scaled percentages.
ScaleUserSteps();
if (backlight_->DeviceExists()) {
const int64_t current_level = backlight_->GetCurrentBrightnessLevel();
current_percent_ = LevelToPercent(current_level);
LOG(INFO) << "Backlight has range [0, "
<< backlight_->GetMaxBrightnessLevel() << "] with initial level "
<< current_level;
}
if (ambient_light_handler_.get()) {
std::string pref_value;
CHECK(prefs_->GetString(kKeyboardBacklightAlsStepsPref, &pref_value))
<< "Unable to read pref " << kKeyboardBacklightAlsStepsPref;
double als_smoothing_constant;
CHECK(prefs_->GetDouble(kAlsSmoothingConstantPref, &als_smoothing_constant))
<< "Failed to read pref " << kAlsSmoothingConstantPref;
ambient_light_handler_->Init(pref_value, current_percent_,
als_smoothing_constant);
} else {
automated_percent_ = user_steps_.back();
prefs_->GetDouble(kKeyboardBacklightNoAlsBrightnessPref,
&automated_percent_);
UpdateState(Transition::SLOW, BacklightBrightnessChange_Cause_OTHER);
}
}
void KeyboardBacklightController::AddObserver(
BacklightControllerObserver* observer) {
DCHECK(observer);
observers_.AddObserver(observer);
}
void KeyboardBacklightController::RemoveObserver(
BacklightControllerObserver* observer) {
DCHECK(observer);
observers_.RemoveObserver(observer);
}
void KeyboardBacklightController::HandlePowerSourceChange(PowerSource source) {}
void KeyboardBacklightController::HandleDisplayModeChange(DisplayMode mode) {}
void KeyboardBacklightController::HandleSessionStateChange(SessionState state) {
session_state_ = state;
if (state == SessionState::STARTED) {
num_als_adjustments_ = 0;
num_user_adjustments_ = 0;
}
}
void KeyboardBacklightController::HandlePowerButtonPress() {}
void KeyboardBacklightController::HandleLidStateChange(LidState state) {
if (state == lid_state_)
return;
lid_state_ = state;
UpdateState(
lid_state_ == LidState::CLOSED ? Transition::INSTANT : Transition::FAST,
BacklightBrightnessChange_Cause_OTHER);
}
void KeyboardBacklightController::HandleUserActivity(UserActivityType type) {
last_user_activity_time_ = clock_->GetCurrentTime();
UpdateTurnOffTimer();
UpdateState(Transition::FAST, BacklightBrightnessChange_Cause_USER_ACTIVITY);
}
void KeyboardBacklightController::HandleVideoActivity(bool is_fullscreen) {
// Ignore fullscreen video that's reported when the user isn't logged in;
// it may be triggered by animations on the login screen.
if (is_fullscreen && session_state_ == SessionState::STOPPED)
is_fullscreen = false;
if (is_fullscreen != fullscreen_video_playing_) {
VLOG(1) << "Fullscreen video "
<< (is_fullscreen ? "started" : "went non-fullscreen");
fullscreen_video_playing_ = is_fullscreen;
UpdateState(Transition::SLOW,
BacklightBrightnessChange_Cause_USER_ACTIVITY);
}
video_timer_.Stop();
if (is_fullscreen) {
video_timer_.Start(
FROM_HERE, base::TimeDelta::FromMilliseconds(kVideoTimeoutIntervalMs),
this, &KeyboardBacklightController::HandleVideoTimeout);
}
}
void KeyboardBacklightController::HandleWakeNotification() {}
void KeyboardBacklightController::HandleHoverStateChange(bool hovering) {
if (!supports_hover_ || hovering == hovering_)
return;
hovering_ = hovering;
turn_off_timer_.Stop();
if (!hovering_) {
// If the user stopped hovering, start a timer to turn the backlight off in
// a little while. If this is updated to do something else instead of
// calling UpdateState(), TestApi::TriggerTurnOffTimeout() must also be
// updated.
last_hover_time_ = clock_->GetCurrentTime();
UpdateTurnOffTimer();
} else {
last_hover_time_ = base::TimeTicks();
}
UpdateState(hovering_ ? Transition::FAST : Transition::SLOW,
BacklightBrightnessChange_Cause_USER_ACTIVITY);
}
void KeyboardBacklightController::HandleTabletModeChange(TabletMode mode) {
if (mode == tablet_mode_)
return;
tablet_mode_ = mode;
UpdateState(Transition::FAST, BacklightBrightnessChange_Cause_OTHER);
}
void KeyboardBacklightController::HandlePolicyChange(
const PowerManagementPolicy& policy) {}
void KeyboardBacklightController::HandleDisplayServiceStart() {}
void KeyboardBacklightController::SetDimmedForInactivity(bool dimmed) {
if (dimmed == dimmed_for_inactivity_)
return;
dimmed_for_inactivity_ = dimmed;
UpdateState(Transition::SLOW,
BacklightBrightnessChange_Cause_USER_INACTIVITY);
}
void KeyboardBacklightController::SetOffForInactivity(bool off) {
if (off == off_for_inactivity_)
return;
off_for_inactivity_ = off;
UpdateState(Transition::SLOW,
BacklightBrightnessChange_Cause_USER_INACTIVITY);
}
void KeyboardBacklightController::SetSuspended(bool suspended) {
if (suspended == suspended_)
return;
suspended_ = suspended;
UpdateState(suspended ? Transition::INSTANT : Transition::FAST,
BacklightBrightnessChange_Cause_OTHER);
if (!suspended && ambient_light_handler_.get())
ambient_light_handler_->HandleResume();
}
void KeyboardBacklightController::SetShuttingDown(bool shutting_down) {
if (shutting_down == shutting_down_)
return;
shutting_down_ = shutting_down;
UpdateState(Transition::INSTANT, BacklightBrightnessChange_Cause_OTHER);
}
void KeyboardBacklightController::SetForcedOff(bool forced_off) {
if (forced_off_ == forced_off)
return;
forced_off_ = forced_off;
UpdateState(Transition::INSTANT,
forced_off
? BacklightBrightnessChange_Cause_FORCED_OFF
: BacklightBrightnessChange_Cause_NO_LONGER_FORCED_OFF);
}
bool KeyboardBacklightController::GetForcedOff() {
return forced_off_;
}
bool KeyboardBacklightController::GetBrightnessPercent(double* percent) {
DCHECK(percent);
*percent = current_percent_;
return true;
}
int KeyboardBacklightController::GetNumAmbientLightSensorAdjustments() const {
return num_als_adjustments_;
}
int KeyboardBacklightController::GetNumUserAdjustments() const {
return num_user_adjustments_;
}
double KeyboardBacklightController::LevelToPercent(int64_t level) const {
const int64_t max_level = backlight_->GetMaxBrightnessLevel();
if (max_level == 0)
return -1.0;
level = std::max(std::min(level, max_level), static_cast<int64_t>(0));
double raw_percent = level * 100.0 / max_level;
return RawPercentToPercent(raw_percent);
}
int64_t KeyboardBacklightController::PercentToLevel(double percent) const {
const int64_t max_level = backlight_->GetMaxBrightnessLevel();
if (max_level == 0)
return -1;
double raw_percent = PercentToRawPercent(util::ClampPercent(percent));
return lround(max_level * raw_percent / 100.0);
}
void KeyboardBacklightController::SetBrightnessPercentForAmbientLight(
double brightness_percent,
AmbientLightHandler::BrightnessChangeCause cause) {
automated_percent_ = brightness_percent;
const bool ambient_light_changed =
cause == AmbientLightHandler::BrightnessChangeCause::AMBIENT_LIGHT;
const Transition transition =
ambient_light_changed ? Transition::SLOW : Transition::FAST;
const BacklightBrightnessChange_Cause backlight_cause =
AmbientLightHandler::ToProtobufCause(cause);
if (UpdateState(transition, backlight_cause) && ambient_light_changed)
num_als_adjustments_++;
}
void KeyboardBacklightController::OnColorTemperatureChanged(
int color_temperature) {}
void KeyboardBacklightController::OnBrightnessChange(
double brightness_percent,
BacklightBrightnessChange_Cause cause,
BacklightController* source) {
DCHECK_EQ(source, display_backlight_controller_);
bool zero = brightness_percent <= kEpsilon;
if (zero != display_brightness_is_zero_) {
display_brightness_is_zero_ = zero;
UpdateState(Transition::SLOW, cause);
}
}
void KeyboardBacklightController::OnBacklightDeviceChanged(
system::BacklightInterface* backlight) {
DCHECK_EQ(backlight, backlight_);
if (backlight_->DeviceExists()) {
const int64_t level = PercentToLevel(current_percent_);
LOG(INFO) << "Restoring brightness " << level << " (" << current_percent_
<< "%) to backlight with range [0, "
<< backlight_->GetMaxBrightnessLevel() << "] and initial level "
<< backlight_->GetCurrentBrightnessLevel();
backlight_->SetBrightnessLevel(level,
GetTransitionDuration(Transition::FAST));
}
}
void KeyboardBacklightController::HandleVideoTimeout() {
if (fullscreen_video_playing_)
VLOG(1) << "Fullscreen video stopped";
fullscreen_video_playing_ = false;
UpdateState(Transition::FAST, BacklightBrightnessChange_Cause_OTHER);
UpdateTurnOffTimer();
}
bool KeyboardBacklightController::RecentlyHoveringOrUserActive() const {
if (hovering_)
return true;
const base::TimeTicks now = clock_->GetCurrentTime();
const base::TimeDelta delay =
fullscreen_video_playing_ ? keep_on_during_video_delay_ : keep_on_delay_;
return (!last_hover_time_.is_null() && (now - last_hover_time_ < delay)) ||
(!last_user_activity_time_.is_null() &&
(now - last_user_activity_time_ < delay));
}
void KeyboardBacklightController::InitUserStepIndex() {
if (user_step_index_ != -1)
return;
// Find the step nearest to the current backlight percent.
double percent_delta = std::numeric_limits<double>::max();
for (size_t i = 0; i < user_steps_.size(); i++) {
double temp_delta = fabs(current_percent_ - user_steps_[i]);
if (temp_delta < percent_delta) {
percent_delta = temp_delta;
user_step_index_ = i;
}
}
CHECK_NE(user_step_index_, -1)
<< "Failed to find brightness step for " << current_percent_ << "%";
}
void KeyboardBacklightController::UpdateTurnOffTimer() {
if (!supports_hover_ && !turn_on_for_user_activity_)
return;
turn_off_timer_.Stop();
// The timer shouldn't start until hovering stops.
if (hovering_)
return;
// Determine how much time is left.
const base::TimeTicks timeout_start =
std::max(last_hover_time_, last_user_activity_time_);
if (timeout_start.is_null())
return;
const base::TimeDelta full_delay =
fullscreen_video_playing_ ? keep_on_during_video_delay_ : keep_on_delay_;
const base::TimeDelta remaining_delay =
full_delay - (clock_->GetCurrentTime() - timeout_start);
if (remaining_delay <= base::TimeDelta::FromMilliseconds(0))
return;
turn_off_timer_.Start(
FROM_HERE, remaining_delay,
base::Bind(base::IgnoreResult(&KeyboardBacklightController::UpdateState),
base::Unretained(this), Transition::SLOW,
BacklightBrightnessChange_Cause_OTHER));
}
void KeyboardBacklightController::HandleIncreaseBrightnessRequest() {
LOG(INFO) << "Got user-triggered request to increase brightness";
if (!backlight_->DeviceExists())
return;
if (user_step_index_ == -1)
InitUserStepIndex();
if (user_step_index_ < static_cast<int>(user_steps_.size()) - 1)
user_step_index_++;
num_user_adjustments_++;
UpdateState(Transition::FAST, BacklightBrightnessChange_Cause_USER_REQUEST);
}
void KeyboardBacklightController::HandleDecreaseBrightnessRequest(
bool allow_off) {
LOG(INFO) << "Got user-triggered request to decrease brightness";
if (!backlight_->DeviceExists())
return;
if (user_step_index_ == -1)
InitUserStepIndex();
if (user_step_index_ > (allow_off ? 0 : 1))
user_step_index_--;
num_user_adjustments_++;
UpdateState(Transition::FAST, BacklightBrightnessChange_Cause_USER_REQUEST);
}
void KeyboardBacklightController::HandleGetBrightnessRequest(
double* percent_out, bool* success_out) {
*percent_out = current_percent_;
*success_out = true;
}
bool KeyboardBacklightController::UpdateState(
Transition transition, BacklightBrightnessChange_Cause cause) {
// Force the backlight off immediately in several special cases.
if (forced_off_ || shutting_down_ || suspended_ ||
lid_state_ == LidState::CLOSED || tablet_mode_ == TabletMode::ON)
return ApplyBrightnessPercent(0.0, transition, cause);
// If the user has asked for a specific brightness level, use it unless the
// user is inactive.
if (user_step_index_ != -1) {
double percent = user_steps_[user_step_index_];
if ((off_for_inactivity_ || dimmed_for_inactivity_) && !hovering_)
percent = off_for_inactivity_ ? 0.0 : std::min(kDimPercent, percent);
return ApplyBrightnessPercent(percent, transition, cause);
}
// If requested, force the backlight on if the user is currently or was
// recently active and off otherwise.
if (supports_hover_ || turn_on_for_user_activity_) {
double percent = RecentlyHoveringOrUserActive() ? automated_percent_ : 0.0;
return ApplyBrightnessPercent(percent, transition, cause);
}
// Force the backlight off for several more lower-priority conditions.
// TODO(crbug.com/623404): Restructure this so the backlight is kept on for at
// least a short period after hovering stops while fullscreen video is
// playing.
if (fullscreen_video_playing_ || display_brightness_is_zero_ ||
off_for_inactivity_) {
return ApplyBrightnessPercent(0.0, transition, cause);
}
if (dimmed_for_inactivity_) {
return ApplyBrightnessPercent(std::min(kDimPercent, automated_percent_),
transition, cause);
}
return ApplyBrightnessPercent(automated_percent_, transition, cause);
}
bool KeyboardBacklightController::ApplyBrightnessPercent(
double percent,
Transition transition,
BacklightBrightnessChange_Cause cause) {
const int64_t level = PercentToLevel(percent);
if (level == PercentToLevel(current_percent_) &&
!backlight_->TransitionInProgress())
return false;
if (!backlight_->DeviceExists()) {
// If the underlying device doesn't exist, save the new percent for later.
current_percent_ = percent;
return false;
}
base::TimeDelta interval = GetTransitionDuration(transition);
LOG(INFO) << "Setting brightness to " << level << " (" << percent
<< "%) over " << interval.InMilliseconds() << " ms";
if (!backlight_->SetBrightnessLevel(level, interval)) {
LOG(ERROR) << "Failed to set brightness";
return false;
}
current_percent_ = percent;
EmitBrightnessChangedSignal(dbus_wrapper_, kKeyboardBrightnessChangedSignal,
percent, cause);
for (BacklightControllerObserver& observer : observers_)
observer.OnBrightnessChange(percent, cause, this);
return true;
}
bool KeyboardBacklightController::ValidateUserSteps(std::string* err_msg) {
if (user_steps_.empty()) {
*err_msg = base::StringPrintf("No user brightness steps defined in %s",
kKeyboardBacklightUserStepsPref);
return false;
}
if (user_steps_[0] != 0.0) {
*err_msg =
base::StringPrintf("%s starts at %f instead of 0.0",
kKeyboardBacklightUserStepsPref, user_steps_[0]);
return false;
}
for (const double& step : user_steps_)
if (step < 0.0 || step > 100.0) {
*err_msg = base::StringPrintf("%s step %f is outside [0.0, 100.0]",
kKeyboardBacklightUserStepsPref, step);
return false;
}
if (user_steps_.end() != std::adjacent_find(user_steps_.begin(),
user_steps_.end(),
std::greater_equal<double>())) {
*err_msg = base::StringPrintf("%s is not strictly increasing",
kKeyboardBacklightUserStepsPref);
return false;
}
return true;
}
void KeyboardBacklightController::ScaleUserSteps() {
size_t num_steps = user_steps_.size();
if (num_steps < 3) {
LOG(INFO) << "Not scaling user steps because there are too few steps";
return;
}
// |user_steps_| is in strictly increasing order.
min_raw_percent_ = user_steps_[0];
max_raw_percent_ = user_steps_[num_steps - 1];
min_visible_raw_percent = user_steps_[1];
for (size_t i = 0; i < num_steps; i++) {
user_steps_[i] = RawPercentToPercent(user_steps_[i]);
}
}
double KeyboardBacklightController::RawPercentToPercent(
double raw_percent) const {
if (user_steps_.size() < 3)
return raw_percent;
raw_percent =
std::max(std::min(raw_percent, max_raw_percent_), min_raw_percent_);
if (raw_percent == min_visible_raw_percent)
return kMinVisiblePercent;
else if (raw_percent > min_visible_raw_percent)
return (raw_percent - min_visible_raw_percent) /
(max_raw_percent_ - min_visible_raw_percent) *
(kMaxPercent - kMinVisiblePercent) +
kMinVisiblePercent;
else // raw_percent < min_visible_raw_percent
return (raw_percent - min_raw_percent_) /
(min_visible_raw_percent - min_raw_percent_) *
(kMinVisiblePercent - kMinPercent) +
kMinPercent;
}
double KeyboardBacklightController::PercentToRawPercent(double percent) const {
if (user_steps_.size() < 3)
return percent;
percent = util::ClampPercent(percent);
if (percent == kMinVisiblePercent)
return min_visible_raw_percent;
else if (percent > kMinVisiblePercent)
return (percent - kMinVisiblePercent) / (kMaxPercent - kMinVisiblePercent) *
(max_raw_percent_ - min_visible_raw_percent) +
min_visible_raw_percent;
else // percent < kMinVisiblePercent
return (percent - kMinPercent) / (kMinVisiblePercent - kMinPercent) *
(min_visible_raw_percent - min_raw_percent_) +
min_raw_percent_;
}
} // namespace policy
} // namespace power_manager