| // 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/state_controller.h" |
| |
| #include <stdint.h> |
| |
| #include <algorithm> |
| #include <cmath> |
| #include <utility> |
| #include <vector> |
| |
| #include <base/bind.h> |
| #include <base/callback.h> |
| #include <base/files/file_util.h> |
| #include <base/logging.h> |
| #include <base/strings/string_number_conversions.h> |
| #include <base/strings/string_util.h> |
| #include <base/strings/stringprintf.h> |
| #include <chromeos/dbus/service_constants.h> |
| #include <update_engine/proto_bindings/update_engine.pb.h> |
| |
| #include "power_manager/common/clock.h" |
| #include "power_manager/common/power_constants.h" |
| #include "power_manager/common/prefs.h" |
| #include "power_manager/common/util.h" |
| #include "power_manager/powerd/system/dbus_wrapper.h" |
| #include "power_manager/proto_bindings/idle.pb.h" |
| |
| namespace power_manager { |
| namespace policy { |
| |
| namespace { |
| |
| // Time to wait for display mode change after resuming with lid still closed |
| // before triggering idle and lid closed action (crbug.com/786721). |
| constexpr base::TimeDelta KWaitForExternalDisplayTimeout = |
| base::TimeDelta::FromSeconds(25); |
| |
| // Time to wait for the display mode and policy after Init() is called. |
| constexpr base::TimeDelta kInitialStateTimeout = |
| base::TimeDelta::FromSeconds(10); |
| |
| // Time to wait for the crash-boot-collect to successfully complete. |
| constexpr base::TimeDelta kCrashBootCollectTimeout = |
| base::TimeDelta::FromMinutes(1); |
| |
| // Time to wait for responses to D-Bus method calls to update_engine. |
| constexpr base::TimeDelta kUpdateEngineDBusTimeout = |
| base::TimeDelta::FromSeconds(3); |
| |
| // Interval between logging the list of current wake locks. |
| constexpr base::TimeDelta kWakeLocksLoggingInterval = |
| base::TimeDelta::FromMinutes(5); |
| |
| // File used by crash_reporter to signal successful collection of per-boot crash |
| // logs. |
| constexpr char kCrashBootCollectorDoneFile[] = |
| "/run/crash_reporter/boot-collector-done"; |
| |
| // Returns |time_ms|, a time in milliseconds, as a |
| // util::TimeDeltaToString()-style string. |
| std::string MsToString(int64_t time_ms) { |
| return util::TimeDeltaToString(base::TimeDelta::FromMilliseconds(time_ms)); |
| } |
| |
| // Returns the minimum positive value after comparing |a| and |b|. If one |
| // is zero or negative, the other is returned. If both are zero or |
| // negative, an empty base::TimeDelta is returned. |
| base::TimeDelta GetMinPositiveTimeDelta(base::TimeDelta a, base::TimeDelta b) { |
| if (a > base::TimeDelta()) { |
| if (b > base::TimeDelta()) { |
| return a < b ? a : b; |
| } else { |
| return a; |
| } |
| } else { |
| return b; |
| } |
| } |
| |
| // Helper function for ScheduleActionTimeout() to compute how long to sleep |
| // before calling UpdateState() to perform the next-occurring action. Given |
| // |now| and an action that should be performed |action_delay| after |
| // |last_activity_time|, updates |timeout| to be the minimum of its current |
| // value and the time to wait before performing the action. Does nothing if |
| // |action_delay| is unset or if the action should've been performed already. |
| void UpdateActionTimeout(base::TimeTicks now, |
| base::TimeTicks last_activity_time, |
| base::TimeDelta action_delay, |
| base::TimeDelta* timeout) { |
| if (action_delay <= base::TimeDelta()) |
| return; |
| |
| const base::TimeTicks action_time = last_activity_time + action_delay; |
| if (action_time > now) |
| *timeout = GetMinPositiveTimeDelta(*timeout, action_time - now); |
| } |
| |
| // Helper function for UpdateState. The general pattern here is: |
| // - If |inactivity_duration| has reached |delay| and |
| // |action_already_performed| says that the controller hasn't yet |
| // performed the corresponding action, then run |callback| and set |
| // |action_already_performed| to ensure that the action doesn't get |
| // performed again the next time this is called. |
| // - If |delay| hasn't been reached, then run |undo_callback| if non-null |
| // to undo the action if needed and reset |action_already_performed| so |
| // that the action can be performed later. |
| void HandleDelay(base::TimeDelta delay, |
| base::TimeDelta inactivity_duration, |
| base::Closure callback, |
| base::Closure undo_callback, |
| const std::string& description, |
| const std::string& undo_description, |
| bool* action_already_performed) { |
| if (delay > base::TimeDelta() && inactivity_duration >= delay) { |
| if (!*action_already_performed) { |
| LOG(INFO) << description << " after " |
| << util::TimeDeltaToString(inactivity_duration); |
| callback.Run(); |
| *action_already_performed = true; |
| } |
| } else if (*action_already_performed) { |
| if (!undo_callback.is_null()) { |
| LOG(INFO) << undo_description; |
| undo_callback.Run(); |
| } |
| *action_already_performed = false; |
| } |
| } |
| |
| // Looks up |name|, an int64_t preference representing milliseconds, in |
| // |prefs|, and returns it as a base::TimeDelta. Returns true on success. |
| bool GetMillisecondPref(PrefsInterface* prefs, |
| const std::string& name, |
| base::TimeDelta* out) { |
| DCHECK(prefs); |
| DCHECK(out); |
| |
| int64_t int_value = 0; |
| if (!prefs->GetInt64(name, &int_value)) |
| return false; |
| |
| *out = base::TimeDelta::FromMilliseconds(int_value); |
| return true; |
| } |
| |
| // Returns a string describing |delays| with each field prefixed by |
| // |prefix|. Helper method for GetPolicyDebugString(). |
| std::string GetPolicyDelaysDebugString( |
| const PowerManagementPolicy::Delays& delays, const std::string& prefix) { |
| std::string str; |
| if (delays.has_screen_dim_ms()) |
| str += prefix + "_dim=" + MsToString(delays.screen_dim_ms()) + " "; |
| if (delays.has_screen_off_ms()) |
| str += prefix + "_screen_off=" + MsToString(delays.screen_off_ms()) + " "; |
| if (delays.has_screen_lock_ms()) |
| str += prefix + "_lock=" + MsToString(delays.screen_lock_ms()) + " "; |
| if (delays.has_idle_warning_ms()) |
| str += prefix + "_idle_warn=" + MsToString(delays.idle_warning_ms()) + " "; |
| if (delays.has_idle_ms()) |
| str += prefix + "_idle=" + MsToString(delays.idle_ms()) + " "; |
| return str; |
| } |
| |
| // Returns a string describing the wake locks in |policy|, or an empty string if |
| // no wake locks are held. |
| std::string GetWakeLocksLogString(const PowerManagementPolicy& policy) { |
| std::string msg; |
| if (policy.screen_wake_lock()) |
| msg += " screen"; |
| if (policy.dim_wake_lock()) |
| msg += " dim"; |
| if (policy.system_wake_lock()) |
| msg += " system"; |
| |
| if (msg.empty()) |
| return std::string(); |
| |
| msg = "Active wake locks:" + msg; |
| if (policy.has_reason()) |
| msg += " (" + policy.reason() + ")"; |
| return msg; |
| } |
| |
| } // namespace |
| |
| StateController::TestApi::TestApi(StateController* controller) |
| : controller_(controller) {} |
| |
| StateController::TestApi::~TestApi() { |
| controller_ = nullptr; |
| } |
| |
| void StateController::TestApi::TriggerActionTimeout() { |
| CHECK(controller_->action_timer_.IsRunning()); |
| controller_->action_timer_.Stop(); |
| controller_->HandleActionTimeout(); |
| } |
| |
| bool StateController::TestApi::TriggerInitialStateTimeout() { |
| if (!controller_->initial_state_timer_.IsRunning()) |
| return false; |
| |
| controller_->initial_state_timer_.Stop(); |
| controller_->HandleInitialStateTimeout(); |
| return true; |
| } |
| |
| bool StateController::TestApi::TriggerWaitForExternalDisplayTimeout() { |
| if (!controller_->WaitingForExternalDisplay()) |
| return false; |
| |
| controller_->wait_for_external_display_timer_.Stop(); |
| controller_->HandleWaitForExternalDisplayTimeout(); |
| return true; |
| } |
| |
| bool StateController::TestApi::TriggerHandleCrashBootCollectTimeout() { |
| if (!controller_->WaitingForCrashBootCollect()) |
| return false; |
| |
| controller_->wait_for_crash_boot_collect_timer_.Stop(); |
| controller_->HandleCrashBootCollectTimeout(); |
| return true; |
| } |
| |
| bool StateController::Delays::operator!=( |
| const StateController::Delays& o) const { |
| // Don't bother checking screen_dim_imminent; it's a synthetic delay that's |
| // based solely on screen_dim. |
| return idle != o.idle || idle_warning != o.idle_warning || |
| screen_off != o.screen_off || screen_dim != o.screen_dim || |
| screen_lock != o.screen_lock; |
| } |
| |
| class StateController::ActivityInfo { |
| public: |
| ActivityInfo() = default; |
| ~ActivityInfo() = default; |
| |
| bool active() const { return active_; } |
| |
| // Returns |now| if the activity is currently active or the last time that it |
| // was active otherwise (or an unset time if it was never active). |
| // |
| // This method provides a convenient shorthand for callers that are trying to |
| // compute a most-recent-activity timestamp across several different |
| // activities. This method's return value should not be compared against the |
| // current time to determine if the activity is currently active; call |
| // active() instead. |
| base::TimeTicks GetLastActiveTime(base::TimeTicks now) const { |
| return active_ ? now : last_active_time_; |
| } |
| |
| // Updates the current state of the activity. |
| void SetActive(bool active, base::TimeTicks now) { |
| if (active == active_) |
| return; |
| |
| active_ = active; |
| last_active_time_ = active ? base::TimeTicks() : now; |
| } |
| |
| private: |
| // True if the activity is currently active. |
| bool active_ = false; |
| |
| // If the activity is currently inactive, the time at which it was last |
| // active. Unset if it is currently active or was never active. |
| base::TimeTicks last_active_time_; |
| |
| DISALLOW_COPY_AND_ASSIGN(ActivityInfo); |
| }; |
| |
| const int StateController::kUserActivityAfterScreenOffIncreaseDelaysMs = 60000; |
| |
| constexpr base::TimeDelta StateController::kScreenDimImminentInterval; |
| |
| // static |
| std::string StateController::GetPolicyDebugString( |
| const PowerManagementPolicy& policy) { |
| std::string str = GetPolicyDelaysDebugString(policy.ac_delays(), "ac"); |
| str += GetPolicyDelaysDebugString(policy.battery_delays(), "battery"); |
| |
| if (policy.has_ac_idle_action()) { |
| str += "ac_idle=" + |
| ActionToString(ProtoActionToAction(policy.ac_idle_action())) + " "; |
| } |
| if (policy.has_battery_idle_action()) { |
| str += "battery_idle=" + |
| ActionToString(ProtoActionToAction(policy.battery_idle_action())) + |
| " "; |
| } |
| if (policy.has_lid_closed_action()) { |
| str += "lid_closed=" + |
| ActionToString(ProtoActionToAction(policy.lid_closed_action())) + |
| " "; |
| } |
| if (policy.has_screen_wake_lock()) { |
| str += |
| "screen_wake_lock=" + base::NumberToString(policy.screen_wake_lock()) + |
| " "; |
| } |
| if (policy.has_dim_wake_lock()) |
| str += |
| "dim_wake_lock=" + base::NumberToString(policy.dim_wake_lock()) + " "; |
| if (policy.has_system_wake_lock()) { |
| str += |
| "system_wake_lock=" + base::NumberToString(policy.system_wake_lock()) + |
| " "; |
| } |
| if (policy.has_use_audio_activity()) |
| str += |
| "use_audio=" + base::NumberToString(policy.use_audio_activity()) + " "; |
| if (policy.has_use_video_activity()) |
| str += |
| "use_video=" + base::NumberToString(policy.use_video_activity()) + " "; |
| if (policy.has_presentation_screen_dim_delay_factor()) { |
| str += "presentation_factor=" + |
| base::NumberToString(policy.presentation_screen_dim_delay_factor()) + |
| " "; |
| } |
| if (policy.has_user_activity_screen_dim_delay_factor()) { |
| str += |
| "user_activity_factor=" + |
| base::NumberToString(policy.user_activity_screen_dim_delay_factor()) + |
| " "; |
| } |
| if (policy.has_wait_for_initial_user_activity()) { |
| str += "wait_for_initial_user_activity=" + |
| base::NumberToString(policy.wait_for_initial_user_activity()) + " "; |
| } |
| if (policy.has_force_nonzero_brightness_for_user_activity()) { |
| str += "force_nonzero_brightness_for_user_activity=" + |
| base::NumberToString( |
| policy.force_nonzero_brightness_for_user_activity()) + |
| " "; |
| } |
| |
| if (policy.has_reason()) |
| str += "(" + policy.reason() + ")"; |
| |
| return str.empty() ? "[empty]" : str; |
| } |
| |
| StateController::StateController() |
| : clock_(std::make_unique<Clock>()), |
| audio_activity_(std::make_unique<ActivityInfo>()), |
| screen_wake_lock_(std::make_unique<ActivityInfo>()), |
| dim_wake_lock_(std::make_unique<ActivityInfo>()), |
| system_wake_lock_(std::make_unique<ActivityInfo>()), |
| wake_lock_logger_(kWakeLocksLoggingInterval), |
| weak_ptr_factory_(this) {} |
| |
| StateController::~StateController() { |
| if (prefs_) |
| prefs_->RemoveObserver(this); |
| } |
| |
| void StateController::Init(Delegate* delegate, |
| PrefsInterface* prefs, |
| system::DBusWrapperInterface* dbus_wrapper, |
| PowerSource power_source, |
| LidState lid_state) { |
| delegate_ = delegate; |
| prefs_ = prefs; |
| prefs_->AddObserver(this); |
| LoadPrefs(); |
| |
| dbus_wrapper_ = dbus_wrapper; |
| dbus_wrapper->ExportMethod( |
| kGetInactivityDelaysMethod, |
| base::Bind(&StateController::HandleGetInactivityDelaysMethodCall, |
| weak_ptr_factory_.GetWeakPtr())); |
| |
| update_engine_dbus_proxy_ = |
| dbus_wrapper_->GetObjectProxy(update_engine::kUpdateEngineServiceName, |
| update_engine::kUpdateEngineServicePath); |
| dbus_wrapper_->RegisterForServiceAvailability( |
| update_engine_dbus_proxy_, |
| base::Bind(&StateController::HandleUpdateEngineAvailable, |
| weak_ptr_factory_.GetWeakPtr())); |
| dbus_wrapper_->RegisterForSignal( |
| update_engine_dbus_proxy_, update_engine::kUpdateEngineInterface, |
| update_engine::kStatusUpdateAdvanced, |
| base::Bind(&StateController::HandleUpdateEngineStatusUpdateSignal, |
| weak_ptr_factory_.GetWeakPtr())); |
| |
| ml_decision_dbus_proxy_ = dbus_wrapper_->GetObjectProxy( |
| chromeos::kMlDecisionServiceName, chromeos::kMlDecisionServicePath); |
| dbus_wrapper_->RegisterForServiceAvailability( |
| ml_decision_dbus_proxy_, |
| base::Bind(&StateController::HandleMlDecisionServiceAvailable, |
| weak_ptr_factory_.GetWeakPtr())); |
| |
| last_user_activity_time_ = clock_->GetCurrentTime(); |
| power_source_ = power_source; |
| lid_state_ = lid_state; |
| |
| initial_state_timer_.Start(FROM_HERE, kInitialStateTimeout, this, |
| &StateController::HandleInitialStateTimeout); |
| |
| crash_boot_collector_watcher_.Watch( |
| base::FilePath(kCrashBootCollectorDoneFile), false, |
| base::Bind(&StateController::MaybeStopWaitForCrashBootCollectTimer, |
| weak_ptr_factory_.GetWeakPtr())); |
| |
| wait_for_crash_boot_collect_timer_.Start( |
| FROM_HERE, kCrashBootCollectTimeout, this, |
| &StateController::HandleCrashBootCollectTimeout); |
| |
| UpdateSettingsAndState(); |
| |
| // Emit the current screen-idle state in case powerd restarted while the |
| // screen was dimmed or turned off. |
| EmitScreenIdleStateChanged(screen_dimmed_, screen_turned_off_); |
| |
| initialized_ = true; |
| } |
| |
| void StateController::HandlePowerSourceChange(PowerSource source) { |
| CHECK(initialized_); |
| if (source == power_source_) |
| return; |
| |
| power_source_ = source; |
| UpdateLastUserActivityTime(); |
| UpdateSettingsAndState(); |
| } |
| |
| void StateController::HandleLidStateChange(LidState state) { |
| CHECK(initialized_); |
| if (state == lid_state_) |
| return; |
| |
| lid_state_ = state; |
| |
| if (lid_state_ == LidState::OPEN) { |
| StopWaitForExternalDisplayTimer(); |
| UpdateLastUserActivityTime(); |
| } |
| UpdateSettingsAndState(); |
| } |
| |
| void StateController::HandleTabletModeChange(TabletMode mode) { |
| DCHECK(initialized_); |
| |
| // We don't care about the mode, but we treat events as user activity. |
| UpdateLastUserActivityTime(); |
| UpdateState(); |
| } |
| |
| void StateController::HandleSessionStateChange(SessionState state) { |
| CHECK(initialized_); |
| if (state == session_state_) |
| return; |
| |
| session_state_ = state; |
| saw_user_activity_soon_after_screen_dim_or_off_ = false; |
| saw_user_activity_during_current_session_ = false; |
| UpdateLastUserActivityTime(); |
| UpdateSettingsAndState(); |
| } |
| |
| void StateController::HandleDisplayModeChange(DisplayMode mode) { |
| CHECK(initialized_); |
| if (mode == display_mode_ && got_initial_display_mode_) |
| return; |
| |
| display_mode_ = mode; |
| |
| if (!got_initial_display_mode_) { |
| got_initial_display_mode_ = true; |
| MaybeStopInitialStateTimer(); |
| } else { |
| UpdateLastUserActivityTime(); |
| } |
| |
| StopWaitForExternalDisplayTimer(); |
| UpdateSettingsAndState(); |
| } |
| |
| void StateController::HandleResume(LidState state) { |
| CHECK(initialized_); |
| |
| lid_state_ = state; |
| // If resumed with closed lid and not in docked mode, let us wait for docked |
| // mode before resuspending again. |
| if (lid_state_ == LidState::CLOSED && !in_docked_mode()) { |
| LOG(INFO) << "Waiting for external display before performing idle or " |
| "lid closed action"; |
| wait_for_external_display_timer_.Start( |
| FROM_HERE, KWaitForExternalDisplayTimeout, this, |
| &StateController::HandleWaitForExternalDisplayTimeout); |
| lid_closed_action_performed_ = false; |
| } else if (lid_state_ == LidState::OPEN || |
| lid_state_ == LidState::NOT_PRESENT || in_docked_mode()) { |
| // Treat resume as a user activity if the lid is not closed or if we are in |
| // docked mode. |
| UpdateLastUserActivityTime(); |
| } |
| UpdateSettingsAndState(); |
| } |
| |
| void StateController::HandlePolicyChange(const PowerManagementPolicy& policy) { |
| CHECK(initialized_); |
| policy_ = policy; |
| if (!got_initial_policy_) { |
| got_initial_policy_ = true; |
| MaybeStopInitialStateTimer(); |
| } |
| |
| const base::TimeTicks now = clock_->GetCurrentTime(); |
| screen_wake_lock_->SetActive(policy.screen_wake_lock(), now); |
| dim_wake_lock_->SetActive(policy.dim_wake_lock(), now); |
| system_wake_lock_->SetActive(policy.system_wake_lock(), now); |
| |
| UpdateSettingsAndState(); |
| |
| // Update the message that periodically lists active wake locks. |
| wake_lock_logger_.OnStateChanged(GetWakeLocksLogString(policy)); |
| } |
| |
| void StateController::HandleUserActivity() { |
| CHECK(initialized_); |
| |
| // Ignore user activity reported while the lid is closed unless we're in |
| // docked mode. |
| if (lid_state_ == LidState::CLOSED && !in_docked_mode()) { |
| LOG(WARNING) << "Ignoring user activity received while lid is closed"; |
| return; |
| } |
| |
| const bool old_saw_user_activity = |
| saw_user_activity_soon_after_screen_dim_or_off_; |
| const bool screen_turned_off_recently = |
| delays_.screen_off > base::TimeDelta() && screen_turned_off_ && |
| (clock_->GetCurrentTime() - screen_turned_off_time_).InMilliseconds() <= |
| kUserActivityAfterScreenOffIncreaseDelaysMs; |
| if (!saw_user_activity_soon_after_screen_dim_or_off_ && |
| ((screen_dimmed_ && !screen_turned_off_) || screen_turned_off_recently)) { |
| LOG(INFO) << "Scaling delays due to user activity while screen was dimmed " |
| << "or soon after it was turned off"; |
| saw_user_activity_soon_after_screen_dim_or_off_ = true; |
| } |
| |
| if (session_state_ == SessionState::STARTED) |
| saw_user_activity_during_current_session_ = true; |
| |
| UpdateLastUserActivityTime(); |
| if (old_saw_user_activity != saw_user_activity_soon_after_screen_dim_or_off_) |
| UpdateSettingsAndState(); |
| else |
| UpdateState(); |
| } |
| |
| void StateController::HandleVideoActivity() { |
| CHECK(initialized_); |
| if (screen_dimmed_ || screen_turned_off_) { |
| LOG(INFO) << "Ignoring video since screen is dimmed or off"; |
| return; |
| } |
| last_video_activity_time_ = clock_->GetCurrentTime(); |
| UpdateState(); |
| } |
| |
| void StateController::HandleWakeNotification() { |
| CHECK(initialized_); |
| |
| // Ignore user activity reported while the lid is closed unless we're in |
| // docked mode. |
| if (lid_state_ == LidState::CLOSED && !in_docked_mode()) { |
| LOG(INFO) << "Ignoring wake notification while lid is closed"; |
| return; |
| } |
| |
| last_wake_notification_time_ = clock_->GetCurrentTime(); |
| UpdateState(); |
| } |
| |
| void StateController::HandleAudioStateChange(bool active) { |
| CHECK(initialized_); |
| audio_activity_->SetActive(active, clock_->GetCurrentTime()); |
| UpdateState(); |
| } |
| |
| void StateController::HandleTpmStatus(int dictionary_attack_count) { |
| if (tpm_dictionary_attack_count_ == dictionary_attack_count) |
| return; |
| |
| tpm_dictionary_attack_count_ = dictionary_attack_count; |
| UpdateSettingsAndState(); |
| } |
| |
| PowerManagementPolicy::Delays StateController::CreateInactivityDelaysProto() |
| const { |
| PowerManagementPolicy::Delays proto; |
| if (!delays_.idle.is_zero()) |
| proto.set_idle_ms(delays_.idle.InMilliseconds()); |
| if (!delays_.idle_warning.is_zero()) |
| proto.set_idle_warning_ms(delays_.idle_warning.InMilliseconds()); |
| if (!delays_.screen_off.is_zero()) |
| proto.set_screen_off_ms(delays_.screen_off.InMilliseconds()); |
| if (!delays_.screen_dim.is_zero()) |
| proto.set_screen_dim_ms(delays_.screen_dim.InMilliseconds()); |
| if (!delays_.screen_lock.is_zero()) |
| proto.set_screen_lock_ms(delays_.screen_lock.InMilliseconds()); |
| return proto; |
| } |
| |
| void StateController::OnPrefChanged(const std::string& pref_name) { |
| CHECK(initialized_); |
| if (pref_name == kDisableIdleSuspendPref || |
| pref_name == kIgnoreExternalPolicyPref) { |
| LOG(INFO) << "Reloading prefs for " << pref_name << " change"; |
| LoadPrefs(); |
| UpdateSettingsAndState(); |
| } |
| } |
| |
| // static |
| std::string StateController::ActionToString(Action action) { |
| switch (action) { |
| case Action::SUSPEND: |
| return "suspend"; |
| case Action::STOP_SESSION: |
| return "logout"; |
| case Action::SHUT_DOWN: |
| return "shutdown"; |
| case Action::DO_NOTHING: |
| return "no-op"; |
| } |
| NOTREACHED() << "Unhandled action " << static_cast<int>(action); |
| return base::StringPrintf("unknown (%d)", static_cast<int>(action)); |
| } |
| |
| // static |
| StateController::Action StateController::ProtoActionToAction( |
| PowerManagementPolicy_Action proto_action) { |
| switch (proto_action) { |
| case PowerManagementPolicy_Action_SUSPEND: |
| return Action::SUSPEND; |
| case PowerManagementPolicy_Action_STOP_SESSION: |
| return Action::STOP_SESSION; |
| case PowerManagementPolicy_Action_SHUT_DOWN: |
| return Action::SHUT_DOWN; |
| case PowerManagementPolicy_Action_DO_NOTHING: |
| return Action::DO_NOTHING; |
| default: |
| NOTREACHED() << "Unhandled action " << static_cast<int>(proto_action); |
| return Action::DO_NOTHING; |
| } |
| } |
| |
| // static |
| void StateController::ScaleDelays(Delays* delays, |
| double screen_dim_scale_factor) { |
| DCHECK(delays); |
| if (screen_dim_scale_factor <= 1.0 || delays->screen_dim <= base::TimeDelta()) |
| return; |
| |
| const base::TimeDelta orig_screen_dim = delays->screen_dim; |
| delays->screen_dim *= screen_dim_scale_factor; |
| |
| const base::TimeDelta diff = delays->screen_dim - orig_screen_dim; |
| if (delays->screen_off > base::TimeDelta()) |
| delays->screen_off += diff; |
| if (delays->screen_lock > base::TimeDelta()) |
| delays->screen_lock += diff; |
| if (delays->idle_warning > base::TimeDelta()) |
| delays->idle_warning += diff; |
| if (delays->idle > base::TimeDelta()) |
| delays->idle += diff; |
| } |
| |
| // static |
| void StateController::SanitizeDelays(Delays* delays) { |
| DCHECK(delays); |
| |
| // Don't try to turn the screen off after performing the idle action. |
| if (delays->screen_off > base::TimeDelta()) |
| delays->screen_off = std::min(delays->screen_off, delays->idle); |
| else |
| delays->screen_off = base::TimeDelta(); |
| |
| // Similarly, don't try to dim the screen after turning it off. |
| if (delays->screen_dim > base::TimeDelta()) { |
| delays->screen_dim = |
| std::min(delays->screen_dim, |
| GetMinPositiveTimeDelta(delays->idle, delays->screen_off)); |
| } else { |
| delays->screen_dim = base::TimeDelta(); |
| } |
| |
| // Cap the idle-warning timeout to the idle-action timeout. |
| if (delays->idle_warning > base::TimeDelta()) |
| delays->idle_warning = std::min(delays->idle_warning, delays->idle); |
| else |
| delays->idle_warning = base::TimeDelta(); |
| |
| // If the lock delay matches or exceeds the idle delay, unset it -- |
| // Chrome's lock-before-suspend setting should be enabled instead. |
| if (delays->screen_lock >= delays->idle || |
| delays->screen_lock < base::TimeDelta()) { |
| delays->screen_lock = base::TimeDelta(); |
| } |
| } |
| |
| // static |
| void StateController::MergeDelaysFromPolicy( |
| const PowerManagementPolicy::Delays& policy_delays, Delays* delays_out) { |
| DCHECK(delays_out); |
| |
| if (policy_delays.has_idle_ms() && policy_delays.idle_ms() >= 0) { |
| delays_out->idle = |
| base::TimeDelta::FromMilliseconds(policy_delays.idle_ms()); |
| } |
| if (policy_delays.has_idle_warning_ms() && |
| policy_delays.idle_warning_ms() >= 0) { |
| delays_out->idle_warning = |
| base::TimeDelta::FromMilliseconds(policy_delays.idle_warning_ms()); |
| } |
| if (policy_delays.has_screen_dim_ms() && policy_delays.screen_dim_ms() >= 0) { |
| delays_out->screen_dim = |
| base::TimeDelta::FromMilliseconds(policy_delays.screen_dim_ms()); |
| } |
| if (policy_delays.has_screen_off_ms() && policy_delays.screen_off_ms() >= 0) { |
| delays_out->screen_off = |
| base::TimeDelta::FromMilliseconds(policy_delays.screen_off_ms()); |
| } |
| if (policy_delays.has_screen_lock_ms() && |
| policy_delays.screen_lock_ms() >= 0) { |
| delays_out->screen_lock = |
| base::TimeDelta::FromMilliseconds(policy_delays.screen_lock_ms()); |
| } |
| } |
| |
| bool StateController::WaitingForInitialState() const { |
| return initial_state_timer_.IsRunning(); |
| } |
| |
| bool StateController::WaitingForExternalDisplay() const { |
| return wait_for_external_display_timer_.IsRunning(); |
| } |
| |
| bool StateController::WaitingForCrashBootCollect() const { |
| return wait_for_crash_boot_collect_timer_.IsRunning(); |
| } |
| |
| bool StateController::WaitingForInitialUserActivity() const { |
| return wait_for_initial_user_activity_ && |
| session_state_ == SessionState::STARTED && |
| !saw_user_activity_during_current_session_; |
| } |
| |
| void StateController::MaybeStopInitialStateTimer() { |
| if (got_initial_display_mode_ && got_initial_policy_) |
| initial_state_timer_.Stop(); |
| } |
| |
| void StateController::StopWaitForExternalDisplayTimer() { |
| wait_for_external_display_timer_.Stop(); |
| } |
| |
| void StateController::MaybeStopWaitForCrashBootCollectTimer( |
| const base::FilePath& path, bool error) { |
| if (base::PathExists(base::FilePath(kCrashBootCollectorDoneFile)) && |
| wait_for_crash_boot_collect_timer_.IsRunning()) { |
| wait_for_crash_boot_collect_timer_.Stop(); |
| if (lid_state_ == LidState::CLOSED) |
| UpdateState(); |
| } |
| } |
| |
| bool StateController::IsIdleBlocked() const { |
| return (use_audio_activity_ && audio_activity_->active()) || |
| screen_wake_lock_->active() || dim_wake_lock_->active() || |
| system_wake_lock_->active(); |
| } |
| |
| bool StateController::IsScreenDimBlocked() const { |
| return screen_wake_lock_->active(); |
| } |
| |
| bool StateController::IsScreenOffBlocked() const { |
| // If HDMI audio is active, the screen needs to be kept on. |
| return IsScreenDimBlocked() || dim_wake_lock_->active() || |
| (delegate_->IsHdmiAudioActive() && audio_activity_->active()); |
| } |
| |
| bool StateController::IsScreenLockBlocked() const { |
| return IsScreenDimBlocked() || dim_wake_lock_->active(); |
| } |
| |
| base::TimeTicks StateController::GetLastActivityTimeForIdle( |
| base::TimeTicks now) const { |
| base::TimeTicks last_time = |
| WaitingForInitialUserActivity() ? now : last_user_activity_time_; |
| if (use_audio_activity_) |
| last_time = std::max(last_time, audio_activity_->GetLastActiveTime(now)); |
| if (use_video_activity_) |
| last_time = std::max(last_time, last_video_activity_time_); |
| last_time = std::max(last_time, last_defer_screen_dim_time_); |
| last_time = std::max(last_time, last_wake_notification_time_); |
| |
| // All types of wake locks defer the idle action. |
| last_time = std::max(last_time, screen_wake_lock_->GetLastActiveTime(now)); |
| last_time = std::max(last_time, dim_wake_lock_->GetLastActiveTime(now)); |
| last_time = std::max(last_time, system_wake_lock_->GetLastActiveTime(now)); |
| |
| return last_time; |
| } |
| |
| base::TimeTicks StateController::GetLastActivityTimeForScreenDim( |
| base::TimeTicks now) const { |
| base::TimeTicks last_time = |
| WaitingForInitialUserActivity() ? now : last_user_activity_time_; |
| if (use_video_activity_) |
| last_time = std::max(last_time, last_video_activity_time_); |
| last_time = std::max(last_time, last_defer_screen_dim_time_); |
| last_time = std::max(last_time, last_wake_notification_time_); |
| |
| // Only full-brightness wake locks keep the screen from dimming. |
| last_time = std::max(last_time, screen_wake_lock_->GetLastActiveTime(now)); |
| |
| return last_time; |
| } |
| |
| base::TimeTicks StateController::GetLastActivityTimeForRequestSmartDim( |
| base::TimeTicks now) const { |
| base::TimeTicks last_time = GetLastActivityTimeForScreenDim(now); |
| last_time = std::max(last_time, last_smart_dim_decision_request_time_); |
| return last_time; |
| } |
| |
| base::TimeTicks StateController::GetLastActivityTimeForScreenOff( |
| base::TimeTicks now) const { |
| base::TimeTicks last_time = GetLastActivityTimeForScreenDim(now); |
| |
| // On-but-dimmed wake locks keep the screen on. |
| last_time = std::max(last_time, dim_wake_lock_->GetLastActiveTime(now)); |
| |
| // If HDMI audio is active, the screen needs to be kept on. |
| if (delegate_->IsHdmiAudioActive()) |
| last_time = std::max(last_time, audio_activity_->GetLastActiveTime(now)); |
| |
| return last_time; |
| } |
| |
| base::TimeTicks StateController::GetLastActivityTimeForScreenLock( |
| base::TimeTicks now) const { |
| // On-but-dimmed wake locks also keep the screen from locking. |
| return std::max(GetLastActivityTimeForScreenDim(now), |
| dim_wake_lock_->GetLastActiveTime(now)); |
| } |
| |
| void StateController::UpdateLastUserActivityTime() { |
| last_user_activity_time_ = clock_->GetCurrentTime(); |
| delegate_->ReportUserActivityMetrics(); |
| } |
| |
| void StateController::LoadPrefs() { |
| prefs_->GetBool(kRequireUsbInputDeviceToSuspendPref, |
| &require_usb_input_device_to_suspend_); |
| prefs_->GetBool(kAvoidSuspendWhenHeadphoneJackPluggedPref, |
| &avoid_suspend_when_headphone_jack_plugged_); |
| prefs_->GetBool(kDisableIdleSuspendPref, &disable_idle_suspend_); |
| prefs_->GetBool(kFactoryModePref, &factory_mode_); |
| prefs_->GetBool(kIgnoreExternalPolicyPref, &ignore_external_policy_); |
| |
| int64_t tpm_threshold = 0; |
| prefs_->GetInt64(kTpmCounterSuspendThresholdPref, &tpm_threshold); |
| tpm_dictionary_attack_suspend_threshold_ = static_cast<int>(tpm_threshold); |
| |
| CHECK( |
| GetMillisecondPref(prefs_, kPluggedSuspendMsPref, &pref_ac_delays_.idle)); |
| CHECK(GetMillisecondPref(prefs_, kPluggedOffMsPref, |
| &pref_ac_delays_.screen_off)); |
| CHECK(GetMillisecondPref(prefs_, kPluggedDimMsPref, |
| &pref_ac_delays_.screen_dim)); |
| |
| CHECK(GetMillisecondPref(prefs_, kUnpluggedSuspendMsPref, |
| &pref_battery_delays_.idle)); |
| CHECK(GetMillisecondPref(prefs_, kUnpluggedOffMsPref, |
| &pref_battery_delays_.screen_off)); |
| CHECK(GetMillisecondPref(prefs_, kUnpluggedDimMsPref, |
| &pref_battery_delays_.screen_dim)); |
| |
| SanitizeDelays(&pref_ac_delays_); |
| SanitizeDelays(&pref_battery_delays_); |
| |
| // Don't wait around for the external policy if the controller has been |
| // instructed to ignore it. |
| if (ignore_external_policy_) { |
| got_initial_policy_ = true; |
| MaybeStopInitialStateTimer(); |
| } |
| } |
| |
| void StateController::UpdateSettingsAndState() { |
| const Action old_idle_action = idle_action_; |
| const Action old_lid_closed_action = lid_closed_action_; |
| const Delays old_delays = delays_; |
| |
| const bool on_ac = power_source_ == PowerSource::AC; |
| const bool presenting = display_mode_ == DisplayMode::PRESENTATION; |
| |
| // Start out with the defaults loaded from the power manager's prefs. |
| idle_action_ = Action::SUSPEND; |
| lid_closed_action_ = Action::SUSPEND; |
| delays_ = on_ac ? pref_ac_delays_ : pref_battery_delays_; |
| use_audio_activity_ = true; |
| use_video_activity_ = true; |
| wait_for_initial_user_activity_ = false; |
| double presentation_factor = 1.0; |
| double user_activity_factor = 1.0; |
| reason_for_ignoring_idle_action_.clear(); |
| |
| // Now update them with values that were set in the policy. |
| if (!ignore_external_policy_) { |
| if (on_ac && policy_.has_ac_idle_action()) |
| idle_action_ = ProtoActionToAction(policy_.ac_idle_action()); |
| else if (!on_ac && policy_.has_battery_idle_action()) |
| idle_action_ = ProtoActionToAction(policy_.battery_idle_action()); |
| if (policy_.has_lid_closed_action()) |
| lid_closed_action_ = ProtoActionToAction(policy_.lid_closed_action()); |
| |
| if (on_ac && policy_.has_ac_delays()) |
| MergeDelaysFromPolicy(policy_.ac_delays(), &delays_); |
| else if (!on_ac && policy_.has_battery_delays()) |
| MergeDelaysFromPolicy(policy_.battery_delays(), &delays_); |
| |
| if (policy_.has_use_audio_activity()) |
| use_audio_activity_ = policy_.use_audio_activity(); |
| if (policy_.has_use_video_activity()) |
| use_video_activity_ = policy_.use_video_activity(); |
| if (policy_.has_presentation_screen_dim_delay_factor()) |
| presentation_factor = policy_.presentation_screen_dim_delay_factor(); |
| if (policy_.has_user_activity_screen_dim_delay_factor()) |
| user_activity_factor = policy_.user_activity_screen_dim_delay_factor(); |
| if (policy_.has_wait_for_initial_user_activity()) { |
| wait_for_initial_user_activity_ = |
| policy_.wait_for_initial_user_activity(); |
| } |
| } |
| |
| if (presenting) |
| ScaleDelays(&delays_, presentation_factor); |
| else if (saw_user_activity_soon_after_screen_dim_or_off_) |
| ScaleDelays(&delays_, user_activity_factor); |
| |
| if (idle_action_ == Action::SUSPEND || idle_action_ == Action::SHUT_DOWN) { |
| // The disable-idle-suspend pref overrides |policy_|. Note that it also |
| // prevents the system from shutting down on idle if no session has been |
| // started. |
| if (disable_idle_suspend_) { |
| idle_action_ = Action::DO_NOTHING; |
| reason_for_ignoring_idle_action_ = |
| "disable_idle_suspend powerd pref is set (done automatically in dev)"; |
| } |
| |
| // Avoid suspending or shutting down due to inactivity while a system |
| // update is being applied on AC power so users on slow connections can |
| // get updates. Continue suspending on lid-close so users don't get |
| // confused, though. |
| if (updater_state_ == UpdaterState::UPDATING && on_ac) { |
| idle_action_ = Action::DO_NOTHING; |
| reason_for_ignoring_idle_action_ = "applying update on AC power"; |
| } |
| |
| // Avoid suspending or shutting down due to inactivity immediately after |
| // resume if we are waiting for external display. |
| if (WaitingForExternalDisplay()) { |
| idle_action_ = Action::DO_NOTHING; |
| reason_for_ignoring_idle_action_ = |
| "waiting for display mode change on resuming with lid closed"; |
| } |
| } |
| |
| // Ignore the lid being closed while presenting to support docked mode. |
| if (presenting) |
| lid_closed_action_ = Action::DO_NOTHING; |
| |
| // Override the idle and lid-closed actions to suspend instead of shutting |
| // down if the TPM dictionary-attack counter is high. |
| if (tpm_dictionary_attack_suspend_threshold_ > 0 && |
| tpm_dictionary_attack_count_ >= |
| tpm_dictionary_attack_suspend_threshold_) { |
| LOG(WARNING) << "TPM dictionary attack count is " |
| << tpm_dictionary_attack_count_ << " (threshold is " |
| << tpm_dictionary_attack_suspend_threshold_ << "); " |
| << "overriding actions to suspend instead of shutting down"; |
| if (idle_action_ == Action::SHUT_DOWN) |
| idle_action_ = Action::SUSPEND; |
| if (lid_closed_action_ == Action::SHUT_DOWN) |
| lid_closed_action_ = Action::SUSPEND; |
| } |
| |
| // Most functionality is disabled in factory mode. |
| if (factory_mode_) { |
| delays_.screen_dim = base::TimeDelta(); |
| delays_.screen_off = base::TimeDelta(); |
| delays_.screen_lock = base::TimeDelta(); |
| lid_closed_action_ = Action::DO_NOTHING; |
| idle_action_ = Action::DO_NOTHING; |
| reason_for_ignoring_idle_action_ = "factory mode is enabled"; |
| } |
| |
| SanitizeDelays(&delays_); |
| |
| delays_.screen_dim_imminent = std::max( |
| delays_.screen_dim - kScreenDimImminentInterval, base::TimeDelta()); |
| |
| // If the idle or lid-closed actions changed, make sure that we perform |
| // the new actions in the event that the system is already idle or the |
| // lid is already closed. |
| if (idle_action_ != old_idle_action) |
| idle_action_performed_ = false; |
| if (lid_closed_action_ != old_lid_closed_action) |
| lid_closed_action_performed_ = false; |
| |
| // Let UpdateState() know if it may need to re-send the warning with an |
| // updated time-until-idle-action. |
| resend_idle_warning_ = sent_idle_warning_ && |
| delays_.idle_warning != base::TimeDelta() && |
| delays_.idle != old_delays.idle; |
| |
| LogSettings(); |
| |
| if (delays_ != old_delays) { |
| dbus_wrapper_->EmitSignalWithProtocolBuffer(kInactivityDelaysChangedSignal, |
| CreateInactivityDelaysProto()); |
| } |
| |
| UpdateState(); |
| } |
| |
| void StateController::LogSettings() { |
| std::vector<std::string> wake_locks; |
| wake_locks.reserve(3); |
| if (screen_wake_lock_->active()) |
| wake_locks.emplace_back("screen"); |
| if (dim_wake_lock_->active()) |
| wake_locks.emplace_back("dim"); |
| if (system_wake_lock_->active()) |
| wake_locks.emplace_back("system"); |
| |
| LOG(INFO) << "Updated settings:" |
| << " dim=" << util::TimeDeltaToString(delays_.screen_dim) |
| << " screen_off=" << util::TimeDeltaToString(delays_.screen_off) |
| << " lock=" << util::TimeDeltaToString(delays_.screen_lock) |
| << " idle_warn=" << util::TimeDeltaToString(delays_.idle_warning) |
| << " idle=" << util::TimeDeltaToString(delays_.idle) << " (" |
| << ActionToString(idle_action_) << ")" |
| << " lid_closed=" << ActionToString(lid_closed_action_) |
| << " use_audio=" << use_audio_activity_ |
| << " use_video=" << use_video_activity_ |
| << " wake_locks=" << base::JoinString(wake_locks, ","); |
| |
| if (wait_for_initial_user_activity_) { |
| LOG(INFO) << "Deferring inactivity-triggered actions until user activity " |
| << "is observed each time a session starts"; |
| } |
| } |
| |
| void StateController::PerformAction(Action action, ActionReason reason) { |
| switch (action) { |
| case Action::SUSPEND: |
| delegate_->Suspend(reason); |
| break; |
| case Action::STOP_SESSION: |
| delegate_->StopSession(); |
| break; |
| case Action::SHUT_DOWN: |
| delegate_->ShutDown(); |
| break; |
| case Action::DO_NOTHING: |
| break; |
| default: |
| NOTREACHED() << "Unhandled action " << static_cast<int>(action); |
| } |
| } |
| |
| StateController::Action StateController::GetIdleAction() const { |
| if (!delegate_->IsOobeCompleted()) { |
| LOG(INFO) << "Not performing idle action without OOBE completed"; |
| return Action::DO_NOTHING; |
| } |
| if (idle_action_ == Action::SUSPEND && require_usb_input_device_to_suspend_ && |
| !delegate_->IsUsbInputDeviceConnected()) { |
| LOG(INFO) << "Not suspending for idle without USB input device"; |
| return Action::DO_NOTHING; |
| } |
| if (idle_action_ == Action::SUSPEND && |
| avoid_suspend_when_headphone_jack_plugged_ && |
| delegate_->IsHeadphoneJackPlugged()) { |
| LOG(INFO) << "Not suspending for idle due to headphone jack"; |
| return Action::DO_NOTHING; |
| } |
| return idle_action_; |
| } |
| |
| void StateController::UpdateState() { |
| base::TimeTicks now = clock_->GetCurrentTime(); |
| base::TimeDelta idle_duration = now - GetLastActivityTimeForIdle(now); |
| base::TimeDelta request_smart_dim_decision_duration = |
| now - GetLastActivityTimeForRequestSmartDim(now); |
| base::TimeDelta screen_dim_duration = |
| now - GetLastActivityTimeForScreenDim(now); |
| base::TimeDelta screen_off_duration = |
| now - GetLastActivityTimeForScreenOff(now); |
| base::TimeDelta screen_lock_duration = |
| now - GetLastActivityTimeForScreenLock(now); |
| |
| if (request_smart_dim_decision_ && ml_decision_service_available_ && |
| delays_.screen_dim_imminent > base::TimeDelta() && |
| request_smart_dim_decision_duration >= delays_.screen_dim_imminent && |
| !waiting_for_smart_dim_decision_ && !screen_dimmed_) { |
| RequestSmartDimDecision(); |
| last_smart_dim_decision_request_time_ = clock_->GetCurrentTime(); |
| waiting_for_smart_dim_decision_ = true; |
| } |
| |
| const bool screen_was_dimmed = screen_dimmed_; |
| HandleDelay(delays_.screen_dim, screen_dim_duration, |
| base::Bind(&Delegate::DimScreen, base::Unretained(delegate_)), |
| base::Bind(&Delegate::UndimScreen, base::Unretained(delegate_)), |
| "Dimming screen", "Undimming screen", &screen_dimmed_); |
| if (screen_dimmed_ && !screen_was_dimmed && audio_activity_->active() && |
| delegate_->IsHdmiAudioActive()) { |
| LOG(INFO) << "Audio is currently being sent to display; screen will not be " |
| << "turned off for inactivity"; |
| } |
| |
| const bool screen_was_turned_off = screen_turned_off_; |
| HandleDelay(delays_.screen_off, screen_off_duration, |
| base::Bind(&Delegate::TurnScreenOff, base::Unretained(delegate_)), |
| base::Bind(&Delegate::TurnScreenOn, base::Unretained(delegate_)), |
| "Turning screen off", "Turning screen on", &screen_turned_off_); |
| if (screen_turned_off_ && !screen_was_turned_off) |
| screen_turned_off_time_ = now; |
| else if (!screen_turned_off_) |
| screen_turned_off_time_ = base::TimeTicks(); |
| |
| HandleDelay(delays_.screen_lock, screen_lock_duration, |
| base::Bind(&Delegate::LockScreen, base::Unretained(delegate_)), |
| base::Closure(), "Locking screen", "", &requested_screen_lock_); |
| |
| if (screen_dimmed_ != screen_was_dimmed || |
| screen_turned_off_ != screen_was_turned_off) { |
| EmitScreenIdleStateChanged(screen_dimmed_, screen_turned_off_); |
| } |
| |
| // The idle-imminent signal is only emitted if an idle action is set. |
| if (delays_.idle_warning > base::TimeDelta() && |
| idle_duration >= delays_.idle_warning && |
| idle_action_ != Action::DO_NOTHING) { |
| if (!sent_idle_warning_ || resend_idle_warning_) { |
| const base::TimeDelta time_until_idle = delays_.idle - idle_duration; |
| LOG(INFO) << "Emitting idle-imminent signal with " |
| << util::TimeDeltaToString(time_until_idle) << " after " |
| << util::TimeDeltaToString(idle_duration); |
| IdleActionImminent proto; |
| proto.set_time_until_idle_action(time_until_idle.ToInternalValue()); |
| dbus_wrapper_->EmitSignalWithProtocolBuffer(kIdleActionImminentSignal, |
| proto); |
| sent_idle_warning_ = true; |
| } |
| } else if (sent_idle_warning_) { |
| sent_idle_warning_ = false; |
| // When resetting the idle-warning trigger, only emit the idle-deferred |
| // signal if the idle action hasn't been performed yet or if it was a |
| // no-op action. |
| if (!idle_action_performed_ || idle_action_ == Action::DO_NOTHING) { |
| LOG(INFO) << "Emitting idle-deferred signal"; |
| dbus_wrapper_->EmitBareSignal(kIdleActionDeferredSignal); |
| } |
| } |
| resend_idle_warning_ = false; |
| |
| Action idle_action_to_perform = Action::DO_NOTHING; |
| if (idle_duration >= delays_.idle) { |
| if (!idle_action_performed_) { |
| if (!reason_for_ignoring_idle_action_.empty()) { |
| LOG(INFO) << "Not performing idle action because " |
| << reason_for_ignoring_idle_action_; |
| } |
| idle_action_to_perform = GetIdleAction(); |
| LOG(INFO) << "Ready to perform idle action (" |
| << ActionToString(idle_action_to_perform) << ") after " |
| << util::TimeDeltaToString(idle_duration); |
| idle_action_performed_ = true; |
| } |
| } else { |
| idle_action_performed_ = false; |
| } |
| |
| Action lid_closed_action_to_perform = Action::DO_NOTHING; |
| // Hold off on the lid-closed action if |
| // 1. The initial display mode or policy hasn't been received. powerd starts |
| // before Chrome's gotten a chance to configure the displays and send the |
| // policy, and we don't want to shut down immediately if the user rebooted |
| // with the lid closed. |
| // 2. Just resumed with lid still closed. Chrome takes a little bit of time |
| // to identify and configure external display and we don't want to suspend |
| // immediately if the device resumes with the lid still closed. |
| // 3. Booted with closed lid and crash_reporter has not yet collected |
| // per-boot crash logs. Look at (crbug.com/988831) for more info. |
| |
| if (lid_state_ == LidState::CLOSED && !WaitingForInitialState() && |
| !WaitingForExternalDisplay() && !WaitingForCrashBootCollect()) { |
| if (!lid_closed_action_performed_) { |
| lid_closed_action_to_perform = lid_closed_action_; |
| LOG(INFO) << "Ready to perform lid-closed action (" |
| << ActionToString(lid_closed_action_to_perform) << ")"; |
| lid_closed_action_performed_ = true; |
| } |
| } else { |
| lid_closed_action_performed_ = false; |
| } |
| |
| if (idle_action_to_perform == Action::SHUT_DOWN || |
| lid_closed_action_to_perform == Action::SHUT_DOWN) { |
| // If either of the actions is shutting down, don't perform the other. |
| PerformAction(Action::SHUT_DOWN, idle_action_to_perform == Action::SHUT_DOWN |
| ? ActionReason::IDLE |
| : ActionReason::LID_CLOSED); |
| } else if (idle_action_to_perform == lid_closed_action_to_perform) { |
| // If both actions are the same, only perform it once. |
| PerformAction(idle_action_to_perform, ActionReason::IDLE); |
| } else { |
| // Otherwise, perform both actions. Note that one or both may be |
| // DO_NOTHING. |
| PerformAction(idle_action_to_perform, ActionReason::IDLE); |
| PerformAction(lid_closed_action_to_perform, ActionReason::LID_CLOSED); |
| } |
| |
| ScheduleActionTimeout(now); |
| } |
| |
| void StateController::ScheduleActionTimeout(base::TimeTicks now) { |
| // Find the minimum of the delays that haven't yet occurred. |
| base::TimeDelta timeout_delay; |
| if (!IsScreenDimBlocked()) { |
| if (request_smart_dim_decision_) { |
| UpdateActionTimeout(now, GetLastActivityTimeForScreenDim(now), |
| delays_.screen_dim_imminent, &timeout_delay); |
| } |
| UpdateActionTimeout(now, GetLastActivityTimeForScreenDim(now), |
| delays_.screen_dim, &timeout_delay); |
| } |
| if (!IsScreenOffBlocked()) { |
| UpdateActionTimeout(now, GetLastActivityTimeForScreenOff(now), |
| delays_.screen_off, &timeout_delay); |
| } |
| if (!IsScreenLockBlocked()) { |
| UpdateActionTimeout(now, GetLastActivityTimeForScreenLock(now), |
| delays_.screen_lock, &timeout_delay); |
| } |
| if (!IsIdleBlocked()) { |
| UpdateActionTimeout(now, GetLastActivityTimeForIdle(now), |
| delays_.idle_warning, &timeout_delay); |
| UpdateActionTimeout(now, GetLastActivityTimeForIdle(now), delays_.idle, |
| &timeout_delay); |
| } |
| |
| if (timeout_delay > base::TimeDelta()) { |
| action_timer_.Start(FROM_HERE, timeout_delay, this, |
| &StateController::HandleActionTimeout); |
| action_timer_time_for_testing_ = now + timeout_delay; |
| } else { |
| action_timer_.Stop(); |
| action_timer_time_for_testing_ = base::TimeTicks(); |
| } |
| } |
| |
| void StateController::HandleActionTimeout() { |
| action_timer_time_for_testing_ = base::TimeTicks(); |
| UpdateState(); |
| } |
| |
| void StateController::HandleInitialStateTimeout() { |
| LOG(INFO) << "Didn't receive initial notification about display mode or " |
| << "policy; using " << DisplayModeToString(display_mode_) |
| << " display mode"; |
| UpdateState(); |
| } |
| |
| void StateController::HandleCrashBootCollectTimeout() { |
| LOG(INFO) << "CrashBootCollect did not complete sucessfully in " |
| << util::TimeDeltaToString(kCrashBootCollectTimeout); |
| if (lid_state_ == LidState::CLOSED) |
| UpdateState(); |
| } |
| |
| void StateController::HandleWaitForExternalDisplayTimeout() { |
| LOG(INFO) << "Didn't receive display mode change notification in " |
| << util::TimeDeltaToString(KWaitForExternalDisplayTimeout) |
| << " on resuming with lid closed"; |
| UpdateSettingsAndState(); |
| } |
| |
| void StateController::HandleGetInactivityDelaysMethodCall( |
| dbus::MethodCall* method_call, |
| dbus::ExportedObject::ResponseSender response_sender) { |
| std::unique_ptr<dbus::Response> response( |
| dbus::Response::FromMethodCall(method_call)); |
| dbus::MessageWriter writer(response.get()); |
| writer.AppendProtoAsArrayOfBytes(CreateInactivityDelaysProto()); |
| response_sender.Run(std::move(response)); |
| } |
| |
| void StateController::HandleUpdateEngineAvailable(bool available) { |
| if (!available) { |
| LOG(ERROR) << "Failed waiting for update engine to become available"; |
| return; |
| } |
| |
| dbus::MethodCall method_call(update_engine::kUpdateEngineInterface, |
| update_engine::kGetStatusAdvanced); |
| std::unique_ptr<dbus::Response> response = dbus_wrapper_->CallMethodSync( |
| update_engine_dbus_proxy_, &method_call, kUpdateEngineDBusTimeout); |
| if (!response) |
| return; |
| |
| HandleUpdateEngineStatusMessage(response.get()); |
| } |
| |
| void StateController::HandleUpdateEngineStatusUpdateSignal( |
| dbus::Signal* signal) { |
| HandleUpdateEngineStatusMessage(signal); |
| } |
| |
| void StateController::HandleUpdateEngineStatusMessage(dbus::Message* message) { |
| DCHECK(message); |
| dbus::MessageReader reader(message); |
| update_engine::StatusResult status; |
| if (!reader.PopArrayOfBytesAsProto(&status)) { |
| LOG(ERROR) << "Unable to read update status args."; |
| return; |
| } |
| |
| update_engine::Operation operation = status.current_operation(); |
| // TODO(crbug.com/977320): Through the protobuf we don't have access to the |
| // string value of the current operation. One way to get around this is to add |
| // a new |current_operation_string| value to the protobuf with the current |
| // operation's string representation for these logging purposes. |
| LOG(INFO) << "Update operation is " << operation; |
| UpdaterState state = UpdaterState::IDLE; |
| if (operation == update_engine::Operation::DOWNLOADING || |
| operation == update_engine::Operation::VERIFYING || |
| operation == update_engine::Operation::FINALIZING) { |
| state = UpdaterState::UPDATING; |
| } else if (operation == update_engine::Operation::UPDATED_NEED_REBOOT) { |
| state = UpdaterState::UPDATED; |
| } |
| |
| if (state == updater_state_) |
| return; |
| |
| updater_state_ = state; |
| UpdateSettingsAndState(); |
| } |
| |
| void StateController::EmitScreenIdleStateChanged(bool dimmed, bool off) { |
| ScreenIdleState proto; |
| proto.set_dimmed(dimmed); |
| proto.set_off(off); |
| dbus_wrapper_->EmitSignalWithProtocolBuffer(kScreenIdleStateChangedSignal, |
| proto); |
| } |
| |
| void StateController::HandleMlDecisionServiceAvailable(bool available) { |
| ml_decision_service_available_ = available; |
| if (!available) { |
| LOG(ERROR) << "Failed waiting for ml decision service to become " |
| "available"; |
| return; |
| } |
| } |
| |
| void StateController::RequestSmartDimDecision() { |
| dbus::MethodCall method_call( |
| chromeos::kMlDecisionServiceInterface, |
| chromeos::kMlDecisionServiceShouldDeferScreenDimMethod); |
| |
| dbus_wrapper_->CallMethodAsync( |
| ml_decision_dbus_proxy_, &method_call, kSmartDimDecisionTimeout, |
| base::Bind(&StateController::HandleSmartDimResponse, |
| weak_ptr_factory_.GetWeakPtr())); |
| } |
| |
| void StateController::HandleSmartDimResponse(dbus::Response* response) { |
| screen_dim_deferred_for_testing_ = false; |
| if (!waiting_for_smart_dim_decision_) { |
| LOG(WARNING) << "Smart dim decision is not being waited for"; |
| return; |
| } |
| |
| waiting_for_smart_dim_decision_ = false; |
| |
| if (screen_dimmed_) { |
| VLOG(1) << "Screen is already dimmed"; |
| return; |
| } |
| |
| if (!response) { |
| LOG(ERROR) << "D-Bus method call to " |
| << chromeos::kMlDecisionServiceInterface << "." |
| << chromeos::kMlDecisionServiceShouldDeferScreenDimMethod |
| << " failed"; |
| return; |
| } |
| |
| dbus::MessageReader reader(response); |
| bool should_defer_screen_dim = false; |
| if (!reader.PopBool(&should_defer_screen_dim)) { |
| LOG(ERROR) << "Unable to read info from " |
| << chromeos::kMlDecisionServiceInterface << "." |
| << chromeos::kMlDecisionServiceShouldDeferScreenDimMethod |
| << " response"; |
| return; |
| } |
| |
| if (!should_defer_screen_dim) { |
| VLOG(1) << "Smart dim decided not to defer screen dimming"; |
| return; |
| } |
| |
| screen_dim_deferred_for_testing_ = true; |
| LOG(INFO) << "Smart dim decided to defer screen dimming"; |
| last_defer_screen_dim_time_ = clock_->GetCurrentTime(); |
| UpdateState(); |
| } |
| |
| } // namespace policy |
| } // namespace power_manager |