0

Revert "ash: night_light: Generalize Night Light scheduler and geoposition"

This reverts commit b08c9a689c.

Reason for revert: Will redesign

Original change's description:
> ash: night_light: Generalize Night Light scheduler and geoposition
>
> - Migrate and generalize automatic Night Light scheduler
> (custom and sunset/sunrise timer) and time zone delegate to its
> own file `time_scheduler_controller`.
> - Move SimpleGeoPosition and TimeOfDay from ash/system/night_light
> to ash/system/time.
> - Modify unittests to support new format.
>
> Bug: 1225865
> Test: Manual and `ash_unittests --gtest_filter=*NightLight*`.
> Change-Id: I5f2fec6e9b22cc2776ab57db90e30f3bdf20ae0c
> Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3017139
> Commit-Queue: Cattalyya Nuengsigkapian <cattalyya@chromium.org>
> Reviewed-by: Kyle Horimoto <khorimoto@chromium.org>
> Reviewed-by: Xiaoqian Dai <xdai@chromium.org>
> Cr-Commit-Position: refs/heads/master@{#901294}

Bug: 1225865
Change-Id: I11884d886c7c8da36af1df0c1e03ef04481fa2c4
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3044603
Commit-Queue: Cattalyya Nuengsigkapian <cattalyya@chromium.org>
Reviewed-by: Ahmed Fakhry <afakhry@chromium.org>
Cr-Commit-Position: refs/heads/master@{#904984}
This commit is contained in:
Cattalyya Nuengsigkapian
2021-07-24 01:22:32 +00:00
committed by Chromium LUCI CQ
parent 91df5cbcca
commit 96d1ca3253
16 changed files with 464 additions and 748 deletions

@ -1164,6 +1164,8 @@ component("ash") {
"system/night_light/night_light_controller_impl.h",
"system/night_light/night_light_feature_pod_controller.cc",
"system/night_light/night_light_feature_pod_controller.h",
"system/night_light/time_of_day.cc",
"system/night_light/time_of_day.h",
"system/overview/overview_button_tray.cc",
"system/overview/overview_button_tray.h",
"system/palette/common_palette_tool.cc",
@ -1319,10 +1321,6 @@ component("ash") {
"system/supervised/supervised_icon_string.h",
"system/system_notification_controller.cc",
"system/system_notification_controller.h",
"system/time/time_of_day.cc",
"system/time/time_of_day.h",
"system/time/time_scheduler_controller.cc",
"system/time/time_scheduler_controller.h",
"system/time/time_tray_item_view.cc",
"system/time/time_tray_item_view.h",
"system/time/time_view.cc",
@ -2301,6 +2299,7 @@ test("ash_unittests") {
"system/network/vpn_list_unittest.cc",
"system/network/wifi_toggle_notification_controller_unittest.cc",
"system/night_light/night_light_controller_unittest.cc",
"system/night_light/time_of_day_unittest.cc",
"system/overview/overview_button_tray_unittest.cc",
"system/palette/mock_palette_tool_delegate.cc",
"system/palette/mock_palette_tool_delegate.h",
@ -2336,7 +2335,6 @@ test("ash_unittests") {
"system/session/logout_confirmation_controller_unittest.cc",
"system/session/session_limit_notification_controller_unittest.cc",
"system/status_area_widget_unittest.cc",
"system/time/time_of_day_unittest.cc",
"system/time/time_tray_item_view_unittest.cc",
"system/time/time_view_unittest.cc",
"system/toast/toast_manager_unittest.cc",

@ -31,7 +31,7 @@
#include "ash/style/ash_color_provider.h"
#include "ash/system/model/clock_model.h"
#include "ash/system/model/system_tray_model.h"
#include "ash/system/time/time_of_day.h"
#include "ash/system/night_light/time_of_day.h"
#include "base/bind.h"
#include "base/i18n/time_formatting.h"
#include "base/memory/ptr_util.h"

@ -280,7 +280,6 @@ component("cpp") {
"shell_window_ids.h",
"shutdown_controller.cc",
"shutdown_controller.h",
"simple_geo_position.h",
"style/color_mode_observer.h",
"style/color_provider.cc",
"style/color_provider.h",

@ -6,7 +6,6 @@
#define ASH_PUBLIC_CPP_NIGHT_LIGHT_CONTROLLER_H_
#include "ash/public/cpp/ash_public_export.h"
#include "ash/public/cpp/simple_geo_position.h"
#include "base/macros.h"
#include "base/observer_list.h"
@ -32,6 +31,22 @@ class ASH_PUBLIC_EXPORT NightLightController {
kMaxValue = kCustom,
};
// Represents a geolocation position fix. It's "simple" because it doesn't
// expose all the parameters of the position interface as defined by the
// Geolocation API Specification:
// https://dev.w3.org/geo/api/spec-source.html#position_interface
// The NightLightController is only interested in valid latitude and
// longitude. It also doesn't require any specific accuracy. The more accurate
// the positions, the more accurate sunset and sunrise times calculations.
// However, an IP-based geoposition is considered good enough.
struct SimpleGeoposition {
bool operator==(const SimpleGeoposition& other) const {
return latitude == other.latitude && longitude == other.longitude;
}
double latitude;
double longitude;
};
class Observer {
public:
// Notifies observers with the new schedule type whenever it changes.

@ -1,27 +0,0 @@
// Copyright 2021 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef ASH_PUBLIC_CPP_SIMPLE_GEO_POSITION_H_
#define ASH_PUBLIC_CPP_SIMPLE_GEO_POSITION_H_
namespace ash {
// Represents a geolocation position fix. It's "simple" because it doesn't
// expose all the parameters of the position interface as defined by the
// Geolocation API Specification:
// https://dev.w3.org/geo/api/spec-source.html#position_interface
// The NightLightController is only interested in valid latitude and
// longitude. It also doesn't require any specific accuracy. The more accurate
// the positions, the more accurate sunset and sunrise times calculations.
// However, an IP-based geoposition is considered good enough.
struct SimpleGeoposition {
bool operator==(const SimpleGeoposition& other) const {
return latitude == other.latitude && longitude == other.longitude;
}
double latitude;
double longitude;
};
} // namespace ash
#endif // ASH_PUBLIC_CPP_SIMPLE_GEO_POSITION_H_

@ -6,12 +6,10 @@
#include "ash/display/screen_orientation_controller.h"
#include "ash/display/screen_orientation_controller_test_api.h"
#include "ash/public/cpp/simple_geo_position.h"
#include "ash/shell.h"
#include "ash/system/machine_learning/user_settings_event.pb.h"
#include "ash/system/night_light/night_light_controller_impl.h"
#include "ash/system/power/power_status.h"
#include "ash/system/time/time_scheduler_controller.h"
#include "ash/test/ash_test_base.h"
#include "ash/wm/tablet_mode/tablet_mode_controller.h"
#include "base/test/simple_test_clock.h"
@ -67,13 +65,12 @@ NetworkStatePropertiesPtr CreateCellularNetwork(int signal_strength) {
return network;
}
class FakeTimeSchedulerDelegate : public TimeSchedulerController::Delegate {
class FakeNightLightDelegate : public NightLightControllerImpl::Delegate {
public:
FakeTimeSchedulerDelegate() = default;
~FakeTimeSchedulerDelegate() override = default;
FakeTimeSchedulerDelegate(const FakeTimeSchedulerDelegate&) = delete;
FakeTimeSchedulerDelegate& operator=(const FakeTimeSchedulerDelegate&) =
delete;
FakeNightLightDelegate() = default;
~FakeNightLightDelegate() override = default;
FakeNightLightDelegate(const FakeNightLightDelegate&) = delete;
FakeNightLightDelegate& operator=(const FakeNightLightDelegate&) = delete;
void SetFakeNow(TimeOfDay time) { fake_now_ = time.ToTimeToday(); }
void SetFakeSunset(TimeOfDay time) { fake_sunset_ = time.ToTimeToday(); }
@ -83,7 +80,8 @@ class FakeTimeSchedulerDelegate : public TimeSchedulerController::Delegate {
base::Time GetNow() const override { return fake_now_; }
base::Time GetSunsetTime() const override { return fake_sunset_; }
base::Time GetSunriseTime() const override { return fake_sunrise_; }
bool SetGeoposition(const SimpleGeoposition& position) override {
bool SetGeoposition(
const NightLightController::SimpleGeoposition& position) override {
return false;
}
bool HasGeoposition() const override { return false; }
@ -262,7 +260,7 @@ TEST_F(UserSettingsEventLoggerTest, TestLogNightLightEvent) {
}
TEST_F(UserSettingsEventLoggerTest, TestNightLightSunsetFeature) {
auto night_light_delegate = std::make_unique<FakeTimeSchedulerDelegate>();
auto night_light_delegate = std::make_unique<FakeNightLightDelegate>();
auto* night_light = night_light_delegate.get();
Shell::Get()->night_light_controller()->SetDelegateForTesting(
std::move(night_light_delegate));

@ -18,8 +18,8 @@
#include "ash/shell.h"
#include "ash/strings/grit/ash_strings.h"
#include "ash/system/model/system_tray_model.h"
#include "ash/system/time/time_scheduler_controller.h"
#include "base/bind.h"
#include "base/i18n/time_formatting.h"
#include "base/logging.h"
#include "base/metrics/histogram_macros.h"
#include "base/numerics/ranges.h"
@ -76,6 +76,12 @@ constexpr char kAutoNightLightNotificationShownHistogram[] =
constexpr char kNotifierId[] = "ash.night_light_controller_impl";
constexpr char kNotificationId[] = "ash.auto_night_light_notify";
// Default start time at 6:00 PM as an offset from 00:00.
constexpr int kDefaultStartTimeOffsetMinutes = 18 * 60;
// Default end time at 6:00 AM as an offset from 00:00.
constexpr int kDefaultEndTimeOffsetMinutes = 6 * 60;
constexpr float kDefaultColorTemperature = 0.5f;
// The duration of the temperature change animation for
@ -97,6 +103,62 @@ constexpr float kMinColorTemperatureInKelvin = 4500;
constexpr float kNeutralColorTemperatureInKelvin = 6500;
constexpr float kMaxColorTemperatureInKelvin = 7500;
class NightLightControllerDelegateImpl
: public NightLightControllerImpl::Delegate {
public:
NightLightControllerDelegateImpl() = default;
NightLightControllerDelegateImpl(const NightLightControllerDelegateImpl&) =
delete;
NightLightControllerDelegateImpl& operator=(
const NightLightControllerDelegateImpl&) = delete;
~NightLightControllerDelegateImpl() override = default;
// ash::NightLightControllerImpl::Delegate:
base::Time GetNow() const override { return base::Time::Now(); }
base::Time GetSunsetTime() const override { return GetSunRiseSet(false); }
base::Time GetSunriseTime() const override { return GetSunRiseSet(true); }
bool SetGeoposition(
const NightLightController::SimpleGeoposition& position) override {
if (geoposition_ && *geoposition_ == position)
return false;
geoposition_ =
std::make_unique<NightLightController::SimpleGeoposition>(position);
return true;
}
bool HasGeoposition() const override { return !!geoposition_; }
private:
// Note that the below computation is intentionally performed every time
// GetSunsetTime() or GetSunriseTime() is called rather than once whenever we
// receive a geoposition (which happens at least once a day). This increases
// the chances of getting accurate values, especially around DST changes.
base::Time GetSunRiseSet(bool sunrise) const {
if (!HasGeoposition()) {
LOG(ERROR) << "Invalid geoposition. Using default time for "
<< (sunrise ? "sunrise." : "sunset.");
return sunrise ? TimeOfDay(kDefaultEndTimeOffsetMinutes).ToTimeToday()
: TimeOfDay(kDefaultStartTimeOffsetMinutes).ToTimeToday();
}
icu::CalendarAstronomer astro(geoposition_->longitude,
geoposition_->latitude);
// For sunset and sunrise times calculations to be correct, the time of the
// icu::CalendarAstronomer object should be set to a time near local noon.
// This avoids having the computation flopping over into an adjacent day.
// See the documentation of icu::CalendarAstronomer::getSunRiseSet().
// Note that the icu calendar works with milliseconds since epoch, and
// base::Time::FromDoubleT() / ToDoubleT() work with seconds since epoch.
const double midday_today_sec =
TimeOfDay(12 * 60).ToTimeToday().ToDoubleT();
astro.setTime(midday_today_sec * 1000.0);
const double sun_rise_set_ms = astro.getSunRiseSet(sunrise);
return base::Time::FromDoubleT(sun_rise_set_ms / 1000.0);
}
std::unique_ptr<NightLightController::SimpleGeoposition> geoposition_;
};
// Returns the color temperature range bucket in which |temperature| resides.
// The range buckets are:
// 0 => Range [0 : 20) (least warm).
@ -276,6 +338,9 @@ class ColorTemperatureAnimation : public gfx::LinearAnimation,
: gfx::LinearAnimation(kManualAnimationDuration,
kNightLightAnimationFrameRate,
this) {}
ColorTemperatureAnimation(const ColorTemperatureAnimation&) = delete;
ColorTemperatureAnimation& operator=(const ColorTemperatureAnimation&) =
delete;
~ColorTemperatureAnimation() override = default;
float target_temperature() const { return target_temperature_; }
@ -332,21 +397,12 @@ class ColorTemperatureAnimation : public gfx::LinearAnimation,
float current_temperature_ = 0.0f;
float target_temperature_ = 0.0f;
DISALLOW_COPY_AND_ASSIGN(ColorTemperatureAnimation);
};
NightLightControllerImpl::NightLightControllerImpl()
: temperature_animation_(std::make_unique<ColorTemperatureAnimation>()),
: delegate_(std::make_unique<NightLightControllerDelegateImpl>()),
temperature_animation_(std::make_unique<ColorTemperatureAnimation>()),
ambient_temperature_(kNeutralColorTemperatureInKelvin),
time_scheduler_controller_(std::make_unique<TimeSchedulerController>(
prefs::kNightLightEnabled,
prefs::kNightLightCachedLatitude,
prefs::kNightLightCachedLongitude,
base::BindRepeating(&NightLightControllerImpl::SetEnabled,
base::Unretained(this)),
base::BindRepeating(
&NightLightControllerImpl::RefreshDisplaysTemperature,
base::Unretained(this)))),
weak_ptr_factory_(this) {
Shell::Get()->session_controller()->AddObserver(this);
Shell::Get()->window_tree_host_manager()->AddObserver(this);
@ -372,12 +428,10 @@ void NightLightControllerImpl::RegisterProfilePrefs(
: ScheduleType::kNone;
registry->RegisterIntegerPref(prefs::kNightLightScheduleType,
static_cast<int>(default_schedule_type));
registry->RegisterIntegerPref(
prefs::kNightLightCustomStartTime,
TimeSchedulerController::kDefaultStartTimeOffsetMinutes);
registry->RegisterIntegerPref(
prefs::kNightLightCustomEndTime,
TimeSchedulerController::kDefaultEndTimeOffsetMinutes);
registry->RegisterIntegerPref(prefs::kNightLightCustomStartTime,
kDefaultStartTimeOffsetMinutes);
registry->RegisterIntegerPref(prefs::kNightLightCustomEndTime,
kDefaultEndTimeOffsetMinutes);
registry->RegisterBooleanPref(prefs::kAmbientColorEnabled, true);
registry->RegisterBooleanPref(prefs::kAutoNightLightNotificationDismissed,
false);
@ -480,10 +534,6 @@ NightLightControllerImpl::ColorScalesFromRemappedTemperatureInKevin(
return {red, green, blue};
}
bool NightLightControllerImpl::is_current_geoposition_from_cache() const {
return time_scheduler_controller_->is_current_geoposition_from_cache();
}
float NightLightControllerImpl::GetColorTemperature() const {
if (active_user_pref_service_)
return active_user_pref_service_->GetDouble(prefs::kNightLightTemperature);
@ -513,7 +563,7 @@ TimeOfDay NightLightControllerImpl::GetCustomStartTime() const {
prefs::kNightLightCustomStartTime));
}
return TimeOfDay(TimeSchedulerController::kDefaultStartTimeOffsetMinutes);
return TimeOfDay(kDefaultStartTimeOffsetMinutes);
}
TimeOfDay NightLightControllerImpl::GetCustomEndTime() const {
@ -522,7 +572,7 @@ TimeOfDay NightLightControllerImpl::GetCustomEndTime() const {
active_user_pref_service_->GetInteger(prefs::kNightLightCustomEndTime));
}
return TimeOfDay(TimeSchedulerController::kDefaultEndTimeOffsetMinutes);
return TimeOfDay(kDefaultEndTimeOffsetMinutes);
}
void NightLightControllerImpl::SetAmbientColorEnabled(bool enabled) {
@ -537,19 +587,14 @@ bool NightLightControllerImpl::GetAmbientColorEnabled() const {
bool NightLightControllerImpl::IsNowWithinSunsetSunrise() const {
// The times below are all on the same calendar day.
auto* delegate = time_scheduler_controller_->delegate();
const base::Time now = delegate->GetNow();
return now < delegate->GetSunriseTime() || now > delegate->GetSunsetTime();
const base::Time now = delegate_->GetNow();
return now < delegate_->GetSunriseTime() || now > delegate_->GetSunsetTime();
}
void NightLightControllerImpl::SetEnabled(
bool enabled,
TimeSchedulerController::TimeSetterSource source) {
void NightLightControllerImpl::SetEnabled(bool enabled,
AnimationDuration animation_type) {
if (active_user_pref_service_) {
animation_duration_ =
source == TimeSchedulerController::TimeSetterSource::kAutomatic
? AnimationDuration::kLong
: AnimationDuration::kShort;
animation_duration_ = animation_type;
active_user_pref_service_->SetBoolean(prefs::kNightLightEnabled, enabled);
}
}
@ -587,7 +632,7 @@ void NightLightControllerImpl::SetCustomEndTime(TimeOfDay end_time) {
}
void NightLightControllerImpl::Toggle() {
SetEnabled(!GetEnabled(), TimeSchedulerController::TimeSetterSource::kUser);
SetEnabled(!GetEnabled(), AnimationDuration::kShort);
}
void NightLightControllerImpl::OnDisplayConfigurationChanged() {
@ -629,13 +674,14 @@ void NightLightControllerImpl::OnActiveUserPrefServiceChanged(
void NightLightControllerImpl::SetCurrentGeoposition(
const SimpleGeoposition& position) {
VLOG(1) << "Received new geoposition.";
time_scheduler_controller_->StoreCachedGeoposition(position);
auto* delegate = time_scheduler_controller_->delegate();
const base::Time previous_sunset = delegate->GetSunsetTime();
const base::Time previous_sunrise = delegate->GetSunriseTime();
is_current_geoposition_from_cache_ = false;
StoreCachedGeoposition(position);
if (!delegate->SetGeoposition(position)) {
const base::Time previous_sunset = delegate_->GetSunsetTime();
const base::Time previous_sunrise = delegate_->GetSunriseTime();
if (!delegate_->SetGeoposition(position)) {
VLOG(1) << "Not refreshing since geoposition hasn't changed";
return;
}
@ -652,9 +698,9 @@ void NightLightControllerImpl::SetCurrentGeoposition(
// toggles should be ignored.
constexpr base::TimeDelta kOneHourDuration = base::TimeDelta::FromHours(1);
const bool keep_manual_toggles_during_schedules =
(delegate->GetSunsetTime() - previous_sunset).magnitude() <
(delegate_->GetSunsetTime() - previous_sunset).magnitude() <
kOneHourDuration &&
(delegate->GetSunriseTime() - previous_sunrise).magnitude() <
(delegate_->GetSunriseTime() - previous_sunrise).magnitude() <
kOneHourDuration;
Refresh(/*did_schedule_change=*/true, keep_manual_toggles_during_schedules);
@ -729,21 +775,8 @@ void NightLightControllerImpl::AmbientColorChanged(
}
void NightLightControllerImpl::SetDelegateForTesting(
std::unique_ptr<TimeSchedulerController::Delegate> delegate) {
time_scheduler_controller_->SetDelegateForTesting( // IN-TEST
std::move(delegate));
}
bool NightLightControllerImpl::GetIsTimerRunningForTesting() {
return time_scheduler_controller_->timer()->IsRunning();
}
base::TimeDelta NightLightControllerImpl::GetCurrentTimerDelayForTesting() {
return time_scheduler_controller_->timer()->GetCurrentDelay();
}
void NightLightControllerImpl::FireTimerForTesting() {
return time_scheduler_controller_->timer()->FireNow();
std::unique_ptr<Delegate> delegate) {
delegate_ = std::move(delegate);
}
message_center::Notification*
@ -752,6 +785,26 @@ NightLightControllerImpl::GetAutoNightLightNotificationForTesting() const {
kNotificationId);
}
bool NightLightControllerImpl::MaybeRestoreSchedule() {
DCHECK(active_user_pref_service_);
DCHECK_NE(GetScheduleType(), ScheduleType::kNone);
auto iter = per_user_schedule_target_state_.find(active_user_pref_service_);
if (iter == per_user_schedule_target_state_.end())
return false;
ScheduleTargetState& target_state = iter->second;
// It may be that the device was suspended for a very long time that the
// target time is no longer valid.
if (target_state.target_time <= delegate_->GetNow())
return false;
VLOG(1) << "Restoring a previous schedule.";
DCHECK_NE(GetEnabled(), target_state.target_status);
ScheduleNextToggle(target_state.target_time - delegate_->GetNow());
return true;
}
bool NightLightControllerImpl::UserHasEverChangedSchedule() const {
return active_user_pref_service_ &&
active_user_pref_service_->HasPrefPath(prefs::kNightLightScheduleType);
@ -801,8 +854,53 @@ void NightLightControllerImpl::
}
}
void NightLightControllerImpl::RefreshDisplaysTemperature() {
const float new_temperature = GetEnabled() ? GetColorTemperature() : 0.0f;
void NightLightControllerImpl::LoadCachedGeopositionIfNeeded() {
DCHECK(active_user_pref_service_);
// Even if there is a geoposition, but it's coming from a previously cached
// value, switching users should load the currently saved values for the
// new user. This is to keep users' prefs completely separate. We only ignore
// the cached values once we have a valid non-cached geoposition from any
// user in the same session.
if (delegate_->HasGeoposition() && !is_current_geoposition_from_cache_)
return;
if (!active_user_pref_service_->HasPrefPath(
prefs::kNightLightCachedLatitude) ||
!active_user_pref_service_->HasPrefPath(
prefs::kNightLightCachedLongitude)) {
VLOG(1) << "No valid current geoposition and no valid cached geoposition"
" are available. Will use default times for sunset / sunrise.";
return;
}
VLOG(1) << "Temporarily using a previously cached geoposition.";
delegate_->SetGeoposition(SimpleGeoposition{
active_user_pref_service_->GetDouble(prefs::kNightLightCachedLatitude),
active_user_pref_service_->GetDouble(prefs::kNightLightCachedLongitude)});
is_current_geoposition_from_cache_ = true;
}
void NightLightControllerImpl::StoreCachedGeoposition(
const SimpleGeoposition& position) {
const SessionControllerImpl* session_controller =
Shell::Get()->session_controller();
for (const auto& user_session : session_controller->GetUserSessions()) {
PrefService* pref_service = session_controller->GetUserPrefServiceForUser(
user_session->user_info.account_id);
if (!pref_service)
continue;
pref_service->SetDouble(prefs::kNightLightCachedLatitude,
position.latitude);
pref_service->SetDouble(prefs::kNightLightCachedLongitude,
position.longitude);
}
}
void NightLightControllerImpl::RefreshDisplaysTemperature(
float color_temperature) {
const float new_temperature = GetEnabled() ? color_temperature : 0.0f;
temperature_animation_->AnimateToNewValue(
new_temperature, animation_duration_ == AnimationDuration::kShort
? kManualAnimationDuration
@ -871,8 +969,7 @@ void NightLightControllerImpl::StartWatchingPrefsChanges() {
void NightLightControllerImpl::InitFromUserPrefs() {
StartWatchingPrefsChanges();
time_scheduler_controller_->LoadCachedGeopositionIfNeeded(
active_user_pref_service_);
LoadCachedGeopositionIfNeeded();
if (GetAmbientColorEnabled())
UpdateAmbientRgbScalingFactors();
Refresh(/*did_schedule_change=*/true,
@ -935,7 +1032,7 @@ void NightLightControllerImpl::OnColorTemperaturePrefChanged() {
UMA_HISTOGRAM_EXACT_LINEAR(
"Ash.NightLight.Temperature", GetTemperatureRange(color_temperature),
5 /* number of buckets defined in GetTemperatureRange() */);
RefreshDisplaysTemperature();
RefreshDisplaysTemperature(color_temperature);
}
void NightLightControllerImpl::OnScheduleTypePrefChanged() {
@ -959,16 +1056,16 @@ void NightLightControllerImpl::Refresh(
bool keep_manual_toggles_during_schedules) {
switch (GetScheduleType()) {
case ScheduleType::kNone:
time_scheduler_controller_->StopTimer();
RefreshDisplaysTemperature();
timer_.Stop();
RefreshDisplaysTemperature(GetColorTemperature());
return;
case ScheduleType::kSunsetToSunrise: {
auto* delegate = time_scheduler_controller_->delegate();
RefreshScheduleTimer(delegate->GetSunsetTime(),
delegate->GetSunriseTime(), did_schedule_change,
case ScheduleType::kSunsetToSunrise:
RefreshScheduleTimer(delegate_->GetSunsetTime(),
delegate_->GetSunriseTime(), did_schedule_change,
keep_manual_toggles_during_schedules);
return;
}
case ScheduleType::kCustom:
RefreshScheduleTimer(
GetCustomStartTime().ToTimeToday(), GetCustomEndTime().ToTimeToday(),
@ -984,13 +1081,139 @@ void NightLightControllerImpl::RefreshScheduleTimer(
bool keep_manual_toggles_during_schedules) {
if (GetScheduleType() == ScheduleType::kNone) {
NOTREACHED();
time_scheduler_controller_->StopTimer();
timer_.Stop();
return;
}
time_scheduler_controller_->RefreshScheduleTimer(
active_user_pref_service_, start_time, end_time, did_schedule_change,
keep_manual_toggles_during_schedules);
if (keep_manual_toggles_during_schedules && MaybeRestoreSchedule()) {
RefreshDisplaysTemperature(GetColorTemperature());
return;
}
// NOTE: Users can set any weird combinations.
const base::Time now = delegate_->GetNow();
if (end_time <= start_time) {
// Example:
// Start: 9:00 PM, End: 6:00 AM.
//
// 6:00 21:00
// <----- + ------------------ + ----->
// | |
// end start
//
// Note that the above times are times of day (today). It is important to
// know where "now" is with respect to these times to decide how to adjust
// them.
if (end_time >= now) {
// If the end time (today) is greater than the time now, this means "now"
// is within the NightLight schedule, and the start time is actually
// yesterday. The above timeline is interpreted as:
//
// 21:00 (-1day) 6:00
// <----- + ----------- + ------ + ----->
// | | |
// start now end
//
start_time -= base::TimeDelta::FromDays(1);
} else {
// Two possibilities here:
// - Either "now" is greater than the end time, but less than start time.
// This means NightLight is outside the schedule, waiting for the next
// start time. The end time is actually a day later.
// - Or "now" is greater than both the start and end times. This means
// NightLight is within the schedule, waiting to turn off at the next
// end time, which is also a day later.
end_time += base::TimeDelta::FromDays(1);
}
}
DCHECK_GE(end_time, start_time);
// The target status that we need to set NightLight to now if a change of
// status is needed immediately.
bool enable_now = false;
// Where are we now with respect to the start and end times?
if (now < start_time) {
// Example:
// Start: 6:00 PM today, End: 6:00 AM tomorrow, Now: 4:00 PM.
//
// <----- + ----------- + ----------- + ----->
// | | |
// now start end
//
// In this case, we need to disable NightLight immediately if it's enabled.
enable_now = false;
} else if (now >= start_time && now < end_time) {
// Example:
// Start: 6:00 PM today, End: 6:00 AM tomorrow, Now: 11:00 PM.
//
// <----- + ----------- + ----------- + ----->
// | | |
// start now end
//
// Start NightLight right away. Our future start time is a day later than
// its current value.
enable_now = true;
start_time += base::TimeDelta::FromDays(1);
} else { // now >= end_time.
// Example:
// Start: 6:00 PM today, End: 10:00 PM today, Now: 11:00 PM.
//
// <----- + ----------- + ----------- + ----->
// | | |
// start end now
//
// In this case, our future start and end times are a day later from their
// current values. NightLight needs to be ended immediately if it's already
// enabled.
enable_now = false;
start_time += base::TimeDelta::FromDays(1);
end_time += base::TimeDelta::FromDays(1);
}
// After the above processing, the start and end time are all in the future.
DCHECK_GE(start_time, now);
DCHECK_GE(end_time, now);
if (did_schedule_change && enable_now != GetEnabled()) {
// If the change in the schedule introduces a change in the status, then
// calling SetEnabled() is all we need, since it will trigger a change in
// the user prefs to which we will respond by calling Refresh(). This will
// end up in this function again, adjusting all the needed schedules.
SetEnabled(enable_now, AnimationDuration::kShort);
return;
}
// We reach here in one of the following conditions:
// 1) If schedule changes don't result in changes in the status, we need to
// explicitly update the timer to re-schedule the next toggle to account for
// any changes.
// 2) The user has just manually toggled the status of NightLight either from
// the System Menu or System Settings. In this case, we respect the user
// wish and maintain the current status that they desire, but we schedule the
// status to be toggled according to the time that corresponds with the
// opposite status of the current one.
ScheduleNextToggle(GetEnabled() ? end_time - now : start_time - now);
RefreshDisplaysTemperature(GetColorTemperature());
}
void NightLightControllerImpl::ScheduleNextToggle(base::TimeDelta delay) {
DCHECK(active_user_pref_service_);
const bool new_status = !GetEnabled();
const base::Time target_time = delegate_->GetNow() + delay;
per_user_schedule_target_state_[active_user_pref_service_] =
ScheduleTargetState{target_time, new_status};
VLOG(1) << "Setting Night Light to toggle to "
<< (new_status ? "enabled" : "disabled") << " at "
<< base::TimeFormatTimeOfDay(target_time);
timer_.Start(FROM_HERE, delay,
base::BindOnce(&NightLightControllerImpl::SetEnabled,
base::Unretained(this), new_status,
AnimationDuration::kLong));
}
} // namespace ash

@ -11,9 +11,7 @@
#include "ash/display/window_tree_host_manager.h"
#include "ash/public/cpp/night_light_controller.h"
#include "ash/public/cpp/session/session_observer.h"
#include "ash/public/cpp/simple_geo_position.h"
#include "ash/system/time/time_of_day.h"
#include "ash/system/time/time_scheduler_controller.h"
#include "ash/system/night_light/time_of_day.h"
#include "base/containers/flat_map.h"
#include "base/memory/weak_ptr.h"
#include "base/time/time.h"
@ -135,8 +133,10 @@ class ASH_EXPORT NightLightControllerImpl
AnimationDuration last_animation_duration() const {
return last_animation_duration_;
}
bool is_current_geoposition_from_cache() const;
base::OneShotTimer* timer() { return &timer_; }
bool is_current_geoposition_from_cache() const {
return is_current_geoposition_from_cache_;
}
float ambient_temperature() const { return ambient_temperature_; }
const gfx::Vector3dF& ambient_rgb_scaling_factors() const {
return ambient_rgb_scaling_factors_;
@ -156,8 +156,7 @@ class ASH_EXPORT NightLightControllerImpl
void UpdateAmbientRgbScalingFactors();
// Set the desired NightLight settings in the current active user prefs.
void SetEnabled(bool enabled,
TimeSchedulerController::TimeSetterSource source);
void SetEnabled(bool enabled, AnimationDuration animation_type);
void SetColorTemperature(float temperature);
void SetScheduleType(ScheduleType type);
void SetCustomStartTime(TimeOfDay start_time);
@ -191,12 +190,7 @@ class ASH_EXPORT NightLightControllerImpl
void Click(const absl::optional<int>& button_index,
const absl::optional<std::u16string>& reply) override;
void SetDelegateForTesting(
std::unique_ptr<TimeSchedulerController::Delegate> delegate);
bool GetIsTimerRunningForTesting();
base::TimeDelta GetCurrentTimerDelayForTesting();
void FireTimerForTesting();
void SetDelegateForTesting(std::unique_ptr<Delegate> delegate);
// Returns the Auto Night Light notification if any is currently shown, or
// nullptr.
@ -222,9 +216,19 @@ class ASH_EXPORT NightLightControllerImpl
// Disables showing the Auto Night Light from now on.
void DisableShowingFutureAutoNightLightNotification();
// Refreshes the displays color transforms based on
// |prefs::kNightLightTemperature|, or 0 if NightLight is turned off.
void RefreshDisplaysTemperature();
// Called only when the active user changes in order to see if we need to use
// a previously cached geoposition value from the active user's prefs.
void LoadCachedGeopositionIfNeeded();
// Called whenever we receive a new geoposition update to cache it in all
// logged-in users' prefs so that it can be used later in the event of not
// being able to retrieve a valid geoposition.
void StoreCachedGeoposition(const SimpleGeoposition& position);
// Refreshes the displays color transforms based on the given
// |color_temperature|, which will be overridden to a value of 0 if NightLight
// is turned off.
void RefreshDisplaysTemperature(float color_temperature);
// Reapplys the current color temperature on the displays without starting a
// new animation or overriding an on-going one towards the same target
@ -278,6 +282,13 @@ class ASH_EXPORT NightLightControllerImpl
bool did_schedule_change,
bool keep_manual_toggles_during_schedules);
// Schedule the upcoming next toggle of NightLight mode. This is used for the
// automatic status changes of NightLight which always use an
// AnimationDurationType::kLong.
void ScheduleNextToggle(base::TimeDelta delay);
std::unique_ptr<Delegate> delegate_;
// The pref service of the currently active user. Can be null in
// ash_unittests.
PrefService* active_user_pref_service_ = nullptr;
@ -289,6 +300,22 @@ class ASH_EXPORT NightLightControllerImpl
std::unique_ptr<ColorTemperatureAnimation> temperature_animation_;
// Tracks the upcoming NightLight state changes per each user due to automatic
// schedules. This can be used to restore a manually toggled status while the
// schedule is being used. See MaybeRestoreSchedule().
struct ScheduleTargetState {
// The time at which NightLight will switch to |target_status| defined
// below.
base::Time target_time;
bool target_status;
};
base::flat_map<PrefService*, ScheduleTargetState>
per_user_schedule_target_state_;
// The timer that schedules the start and end of NightLight when the schedule
// type is either kSunsetToSunrise or kCustom.
base::OneShotTimer timer_;
// True only until Night Light is initialized from the very first user
// session. After that, it is set to false.
bool is_first_user_init_ = true;
@ -317,10 +344,6 @@ class ASH_EXPORT NightLightControllerImpl
// Valid only if ambient color is enabled.
gfx::Vector3dF ambient_rgb_scaling_factors_ = {1.f, 1.f, 1.f};
// |time_scheduler_controller_| helps schedule a timer to automatically turn
// on and off the night light mode from start and end time in user's prefs.
std::unique_ptr<TimeSchedulerController> time_scheduler_controller_;
base::WeakPtrFactory<NightLightControllerImpl> weak_ptr_factory_;
DISALLOW_COPY_AND_ASSIGN(NightLightControllerImpl);

@ -12,14 +12,12 @@
#include "ash/display/cursor_window_controller.h"
#include "ash/display/window_tree_host_manager.h"
#include "ash/public/cpp/session/session_types.h"
#include "ash/public/cpp/simple_geo_position.h"
#include "ash/root_window_controller.h"
#include "ash/session/session_controller_impl.h"
#include "ash/session/test_session_controller_client.h"
#include "ash/shell.h"
#include "ash/system/night_light/night_light_controller_impl.h"
#include "ash/system/time/time_of_day.h"
#include "ash/system/time/time_scheduler_controller.h"
#include "ash/system/night_light/time_of_day.h"
#include "ash/test/ash_test_base.h"
#include "ash/test/ash_test_helper.h"
#include "ash/test_shell_delegate.h"
@ -148,7 +146,7 @@ constexpr double kFakePosition2_Longitude = -100.5;
constexpr int kFakePosition2_SunsetOffset = 17 * 60;
constexpr int kFakePosition2_SunriseOffset = 3 * 60;
class TestDelegate : public TimeSchedulerController::Delegate {
class TestDelegate : public NightLightControllerImpl::Delegate {
public:
TestDelegate() = default;
TestDelegate(const TestDelegate& other) = delete;
@ -164,15 +162,17 @@ class TestDelegate : public TimeSchedulerController::Delegate {
base::Time GetNow() const override { return fake_now_; }
base::Time GetSunsetTime() const override { return fake_sunset_; }
base::Time GetSunriseTime() const override { return fake_sunrise_; }
bool SetGeoposition(const SimpleGeoposition& position) override {
bool SetGeoposition(
const NightLightController::SimpleGeoposition& position) override {
has_geoposition_ = true;
if (position ==
SimpleGeoposition{kFakePosition1_Latitude, kFakePosition1_Longitude}) {
if (position == NightLightController::SimpleGeoposition{
kFakePosition1_Latitude, kFakePosition1_Longitude}) {
// Set sunset and sunrise times associated with fake position 1.
SetFakeSunset(TimeOfDay(kFakePosition1_SunsetOffset));
SetFakeSunrise(TimeOfDay(kFakePosition1_SunriseOffset));
} else if (position == SimpleGeoposition{kFakePosition2_Latitude,
kFakePosition2_Longitude}) {
} else if (position ==
NightLightController::SimpleGeoposition{
kFakePosition2_Latitude, kFakePosition2_Longitude}) {
// Set sunset and sunrise times associated with fake position 2.
SetFakeSunset(TimeOfDay(kFakePosition2_SunsetOffset));
SetFakeSunrise(TimeOfDay(kFakePosition2_SunriseOffset));
@ -234,7 +234,7 @@ class NightLightTest : public NoSessionAshTestBase {
void SetNightLightEnabled(bool enabled) {
GetController()->SetEnabled(
enabled, TimeSchedulerController::TimeSetterSource::kUser);
enabled, NightLightControllerImpl::AnimationDuration::kShort);
}
void SetAmbientColorPrefEnabled(bool enabled) {
@ -461,16 +461,16 @@ TEST_F(NightLightTest, TestScheduleNoneToCustomTransition) {
TestCompositorsTemperature(controller->GetColorTemperature());
EXPECT_EQ(NightLightControllerImpl::AnimationDuration::kShort,
controller->last_animation_duration());
EXPECT_TRUE(controller->GetIsTimerRunningForTesting());
EXPECT_TRUE(controller->timer()->IsRunning());
EXPECT_EQ(base::TimeDelta::FromHours(2),
controller->GetCurrentTimerDelayForTesting());
controller->timer()->GetCurrentDelay());
// If the user changes the schedule type to "none", the NightLight status
// should not change, but the timer should not be running.
controller->SetScheduleType(NightLightController::ScheduleType::kNone);
EXPECT_TRUE(controller->GetEnabled());
TestCompositorsTemperature(controller->GetColorTemperature());
EXPECT_FALSE(controller->GetIsTimerRunningForTesting());
EXPECT_FALSE(controller->timer()->IsRunning());
}
// Tests what happens when the time now reaches the end of the NightLight
@ -494,16 +494,16 @@ TEST_F(NightLightTest, TestCustomScheduleReachingEndTime) {
//
// Now is 8:00 PM.
delegate()->SetFakeNow(TimeOfDay(20 * 60));
controller->FireTimerForTesting();
controller->timer()->FireNow();
EXPECT_FALSE(controller->GetEnabled());
TestCompositorsTemperature(0.0f);
EXPECT_EQ(NightLightControllerImpl::AnimationDuration::kLong,
controller->last_animation_duration());
// The timer should still be running, but now scheduling the start at 3:00 PM
// tomorrow which is 19 hours from "now" (8:00 PM).
EXPECT_TRUE(controller->GetIsTimerRunningForTesting());
EXPECT_TRUE(controller->timer()->IsRunning());
EXPECT_EQ(base::TimeDelta::FromHours(19),
controller->GetCurrentTimerDelayForTesting());
controller->timer()->GetCurrentDelay());
}
// Tests that user toggles from the system menu or system settings override any
@ -536,9 +536,9 @@ TEST_F(NightLightTest, TestExplicitUserTogglesWhileScheduleIsActive) {
controller->last_animation_duration());
// The timer should still be running, but NightLight should automatically
// turn off at 8:00 PM tomorrow, which is 21 hours from now (11:00 PM).
EXPECT_TRUE(controller->GetIsTimerRunningForTesting());
EXPECT_TRUE(controller->timer()->IsRunning());
EXPECT_EQ(base::TimeDelta::FromHours(21),
controller->GetCurrentTimerDelayForTesting());
controller->timer()->GetCurrentDelay());
// Manually turning it back off should also be respected, and this time the
// start is scheduled at 3:00 PM tomorrow after 19 hours from "now" (8:00 PM).
@ -547,9 +547,9 @@ TEST_F(NightLightTest, TestExplicitUserTogglesWhileScheduleIsActive) {
TestCompositorsTemperature(0.0f);
EXPECT_EQ(NightLightControllerImpl::AnimationDuration::kShort,
controller->last_animation_duration());
EXPECT_TRUE(controller->GetIsTimerRunningForTesting());
EXPECT_TRUE(controller->timer()->IsRunning());
EXPECT_EQ(base::TimeDelta::FromHours(16),
controller->GetCurrentTimerDelayForTesting());
controller->timer()->GetCurrentDelay());
}
// Tests that changing the custom start and end times, in such a way that
@ -574,27 +574,27 @@ TEST_F(NightLightTest, TestChangingStartTimesThatDontChangeTheStatus) {
controller->SetScheduleType(NightLightController::ScheduleType::kCustom);
EXPECT_FALSE(controller->GetEnabled());
TestCompositorsTemperature(0.0f);
EXPECT_TRUE(controller->GetIsTimerRunningForTesting());
EXPECT_TRUE(controller->timer()->IsRunning());
EXPECT_EQ(base::TimeDelta::FromHours(2),
controller->GetCurrentTimerDelayForTesting());
controller->timer()->GetCurrentDelay());
// Change the start time in such a way that doesn't change the status, but
// despite that, confirm that schedule has been updated.
controller->SetCustomStartTime(TimeOfDay(19 * 60)); // 7:00 PM.
EXPECT_FALSE(controller->GetEnabled());
TestCompositorsTemperature(0.0f);
EXPECT_TRUE(controller->GetIsTimerRunningForTesting());
EXPECT_TRUE(controller->timer()->IsRunning());
EXPECT_EQ(base::TimeDelta::FromHours(3),
controller->GetCurrentTimerDelayForTesting());
controller->timer()->GetCurrentDelay());
// Changing the end time in a similar fashion to the above and expect no
// change.
controller->SetCustomEndTime(TimeOfDay(23 * 60)); // 11:00 PM.
EXPECT_FALSE(controller->GetEnabled());
TestCompositorsTemperature(0.0f);
EXPECT_TRUE(controller->GetIsTimerRunningForTesting());
EXPECT_TRUE(controller->timer()->IsRunning());
EXPECT_EQ(base::TimeDelta::FromHours(3),
controller->GetCurrentTimerDelayForTesting());
controller->timer()->GetCurrentDelay());
}
// Tests the behavior of the sunset to sunrise automatic schedule type.
@ -619,33 +619,33 @@ TEST_F(NightLightTest, TestSunsetSunrise) {
NightLightController::ScheduleType::kSunsetToSunrise);
EXPECT_FALSE(controller->GetEnabled());
TestCompositorsTemperature(0.0f);
EXPECT_TRUE(controller->GetIsTimerRunningForTesting());
EXPECT_TRUE(controller->timer()->IsRunning());
EXPECT_EQ(base::TimeDelta::FromHours(4),
controller->GetCurrentTimerDelayForTesting());
controller->timer()->GetCurrentDelay());
// Simulate reaching sunset.
delegate()->SetFakeNow(TimeOfDay(20 * 60)); // Now is 8:00 PM.
controller->FireTimerForTesting();
controller->timer()->FireNow();
EXPECT_TRUE(controller->GetEnabled());
TestCompositorsTemperature(controller->GetColorTemperature());
EXPECT_EQ(NightLightControllerImpl::AnimationDuration::kLong,
controller->last_animation_duration());
// Timer is running scheduling the end at sunrise.
EXPECT_TRUE(controller->GetIsTimerRunningForTesting());
EXPECT_TRUE(controller->timer()->IsRunning());
EXPECT_EQ(base::TimeDelta::FromHours(9),
controller->GetCurrentTimerDelayForTesting());
controller->timer()->GetCurrentDelay());
// Simulate reaching sunrise.
delegate()->SetFakeNow(TimeOfDay(5 * 60)); // Now is 5:00 AM.
controller->FireTimerForTesting();
controller->timer()->FireNow();
EXPECT_FALSE(controller->GetEnabled());
TestCompositorsTemperature(0.0f);
EXPECT_EQ(NightLightControllerImpl::AnimationDuration::kLong,
controller->last_animation_duration());
// Timer is running scheduling the start at the next sunset.
EXPECT_TRUE(controller->GetIsTimerRunningForTesting());
EXPECT_TRUE(controller->timer()->IsRunning());
EXPECT_EQ(base::TimeDelta::FromHours(15),
controller->GetCurrentTimerDelayForTesting());
controller->timer()->GetCurrentDelay());
}
// Tests the behavior of the sunset to sunrise automatic schedule type when the
@ -660,29 +660,29 @@ TEST_F(NightLightTest, TestSunsetSunriseGeoposition) {
//
NightLightControllerImpl* controller = GetController();
delegate()->SetFakeNow(TimeOfDay(16 * 60)); // 4:00PM.
controller->SetCurrentGeoposition(
SimpleGeoposition{kFakePosition1_Latitude, kFakePosition1_Longitude});
controller->SetCurrentGeoposition(NightLightController::SimpleGeoposition{
kFakePosition1_Latitude, kFakePosition1_Longitude});
// Expect that timer is running and the start is scheduled after 4 hours.
controller->SetScheduleType(
NightLightController::ScheduleType::kSunsetToSunrise);
EXPECT_FALSE(controller->GetEnabled());
TestCompositorsTemperature(0.0f);
EXPECT_TRUE(controller->GetIsTimerRunningForTesting());
EXPECT_TRUE(controller->timer()->IsRunning());
EXPECT_EQ(base::TimeDelta::FromHours(4),
controller->GetCurrentTimerDelayForTesting());
controller->timer()->GetCurrentDelay());
// Simulate reaching sunset.
delegate()->SetFakeNow(TimeOfDay(20 * 60)); // Now is 8:00 PM.
controller->FireTimerForTesting();
controller->timer()->FireNow();
EXPECT_TRUE(controller->GetEnabled());
TestCompositorsTemperature(controller->GetColorTemperature());
EXPECT_EQ(NightLightControllerImpl::AnimationDuration::kLong,
controller->last_animation_duration());
// Timer is running scheduling the end at sunrise.
EXPECT_TRUE(controller->GetIsTimerRunningForTesting());
EXPECT_TRUE(controller->timer()->IsRunning());
EXPECT_EQ(base::TimeDelta::FromHours(8),
controller->GetCurrentTimerDelayForTesting());
controller->timer()->GetCurrentDelay());
// Now simulate user changing position.
// Position 2 sunset and sunrise times.
@ -692,28 +692,28 @@ TEST_F(NightLightTest, TestSunsetSunriseGeoposition) {
// | | |
// sunset now sunrise
//
controller->SetCurrentGeoposition(
SimpleGeoposition{kFakePosition2_Latitude, kFakePosition2_Longitude});
controller->SetCurrentGeoposition(NightLightController::SimpleGeoposition{
kFakePosition2_Latitude, kFakePosition2_Longitude});
// Expect that the scheduled end delay has been updated, and the status hasn't
// changed.
EXPECT_TRUE(controller->GetEnabled());
TestCompositorsTemperature(controller->GetColorTemperature());
EXPECT_TRUE(controller->GetIsTimerRunningForTesting());
EXPECT_TRUE(controller->timer()->IsRunning());
EXPECT_EQ(base::TimeDelta::FromHours(7),
controller->GetCurrentTimerDelayForTesting());
controller->timer()->GetCurrentDelay());
// Simulate reaching sunrise.
delegate()->SetFakeNow(TimeOfDay(3 * 60)); // Now is 5:00 AM.
controller->FireTimerForTesting();
controller->timer()->FireNow();
EXPECT_FALSE(controller->GetEnabled());
TestCompositorsTemperature(0.0f);
EXPECT_EQ(NightLightControllerImpl::AnimationDuration::kLong,
controller->last_animation_duration());
// Timer is running scheduling the start at the next sunset.
EXPECT_TRUE(controller->GetIsTimerRunningForTesting());
EXPECT_TRUE(controller->timer()->IsRunning());
EXPECT_EQ(base::TimeDelta::FromHours(14),
controller->GetCurrentTimerDelayForTesting());
controller->timer()->GetCurrentDelay());
}
// Tests the behavior when the client sets the geoposition while in custom
@ -746,16 +746,16 @@ TEST_F(NightLightTest, DISABLED_TestCustomScheduleGeopositionChanges) {
int fake_now = 16 * 60;
delegate()->SetFakeNow(TimeOfDay(fake_now));
controller->SetCurrentGeoposition(
SimpleGeoposition{kFakePosition1_Latitude, kFakePosition1_Longitude});
controller->SetCurrentGeoposition(NightLightController::SimpleGeoposition{
kFakePosition1_Latitude, kFakePosition1_Longitude});
// Expect that timer is running and is scheduled at next custom start time.
controller->SetScheduleType(NightLightController::ScheduleType::kCustom);
EXPECT_FALSE(controller->GetEnabled());
TestCompositorsTemperature(0.0f);
EXPECT_TRUE(controller->GetIsTimerRunningForTesting());
EXPECT_TRUE(controller->timer()->IsRunning());
EXPECT_EQ(base::TimeDelta::FromMinutes(time_diff(fake_now, kCustom_Start)),
controller->GetCurrentTimerDelayForTesting());
controller->timer()->GetCurrentDelay());
// Simulate a timezone change by changing geoposition.
// Current time updates to 9PM.
@ -766,9 +766,9 @@ TEST_F(NightLightTest, DISABLED_TestCustomScheduleGeopositionChanges) {
//
fake_now = 21 * 60;
delegate()->SetFakeNow(TimeOfDay(fake_now));
controller->FireTimerForTesting();
controller->SetCurrentGeoposition(
SimpleGeoposition{kFakePosition2_Latitude, kFakePosition2_Longitude});
controller->timer()->FireNow();
controller->SetCurrentGeoposition(NightLightController::SimpleGeoposition{
kFakePosition2_Latitude, kFakePosition2_Longitude});
// Expect the controller to enter night light mode and the scheduled end
// delay has been updated.
@ -776,26 +776,26 @@ TEST_F(NightLightTest, DISABLED_TestCustomScheduleGeopositionChanges) {
TestCompositorsTemperature(controller->GetColorTemperature());
EXPECT_EQ(NightLightControllerImpl::AnimationDuration::kShort,
controller->last_animation_duration());
EXPECT_TRUE(controller->GetIsTimerRunningForTesting());
EXPECT_TRUE(controller->timer()->IsRunning());
EXPECT_EQ(base::TimeDelta::FromMinutes(time_diff(fake_now, kCustom_End)),
controller->GetCurrentTimerDelayForTesting());
controller->timer()->GetCurrentDelay());
// Simulate user changing position back to location 1 and current time goes
// back to 4PM.
fake_now = 16 * 60;
delegate()->SetFakeNow(TimeOfDay(fake_now));
controller->FireTimerForTesting();
controller->timer()->FireNow();
controller->SetCurrentGeoposition(
SimpleGeoposition{kFakePosition1_Latitude, kFakePosition1_Longitude});
controller->SetCurrentGeoposition(NightLightController::SimpleGeoposition{
kFakePosition1_Latitude, kFakePosition1_Longitude});
EXPECT_FALSE(controller->GetEnabled());
TestCompositorsTemperature(0.0f);
EXPECT_EQ(NightLightControllerImpl::AnimationDuration::kShort,
controller->last_animation_duration());
// Timer is running and is scheduled at next custom start time.
EXPECT_TRUE(controller->GetIsTimerRunningForTesting());
EXPECT_TRUE(controller->timer()->IsRunning());
EXPECT_EQ(base::TimeDelta::FromMinutes(time_diff(fake_now, kCustom_Start)),
controller->GetCurrentTimerDelayForTesting());
controller->timer()->GetCurrentDelay());
}
// Tests the behavior when there is no valid geoposition for example due to lack
@ -848,8 +848,8 @@ TEST_F(NightLightTest, AbsentValidGeoposition) {
EXPECT_EQ(delegate()->GetSunriseTime(), kSunrise1.ToTimeToday());
// Now simulate receiving a geoposition update of fake geoposition 2.
controller->SetCurrentGeoposition(
SimpleGeoposition{kFakePosition2_Latitude, kFakePosition2_Longitude});
controller->SetCurrentGeoposition(NightLightController::SimpleGeoposition{
kFakePosition2_Latitude, kFakePosition2_Longitude});
EXPECT_TRUE(delegate()->HasGeoposition());
EXPECT_FALSE(controller->is_current_geoposition_from_cache());
EXPECT_EQ(delegate()->GetSunsetTime(), kSunset2.ToTimeToday());
@ -877,8 +877,8 @@ TEST_F(NightLightTest, AbsentValidGeoposition) {
user2_pref_service()->ClearPref(prefs::kNightLightCachedLongitude);
// Now simulate receiving a geoposition update of fake geoposition 1.
controller->SetCurrentGeoposition(
SimpleGeoposition{kFakePosition1_Latitude, kFakePosition1_Longitude});
controller->SetCurrentGeoposition(NightLightController::SimpleGeoposition{
kFakePosition1_Latitude, kFakePosition1_Longitude});
EXPECT_TRUE(delegate()->HasGeoposition());
EXPECT_FALSE(controller->is_current_geoposition_from_cache());
EXPECT_EQ(delegate()->GetSunsetTime(), kSunset1.ToTimeToday());
@ -914,10 +914,10 @@ TEST_F(NightLightTest, TestCustomScheduleOnResume) {
EXPECT_FALSE(controller->GetEnabled());
TestCompositorsTemperature(0.0f);
EXPECT_TRUE(controller->GetIsTimerRunningForTesting());
EXPECT_TRUE(controller->timer()->IsRunning());
// NightLight should start in 2 hours.
EXPECT_EQ(base::TimeDelta::FromHours(2),
controller->GetCurrentTimerDelayForTesting());
controller->timer()->GetCurrentDelay());
// Now simulate that the device was suspended for 3 hours, and the time now
// is 7:00 PM when the devices was resumed. Expect that NightLight turns on.
@ -926,10 +926,10 @@ TEST_F(NightLightTest, TestCustomScheduleOnResume) {
EXPECT_TRUE(controller->GetEnabled());
TestCompositorsTemperature(0.4f);
EXPECT_TRUE(controller->GetIsTimerRunningForTesting());
EXPECT_TRUE(controller->timer()->IsRunning());
// NightLight should end in 3 hours.
EXPECT_EQ(base::TimeDelta::FromHours(3),
controller->GetCurrentTimerDelayForTesting());
controller->timer()->GetCurrentDelay());
}
// The following tests ensure that the NightLight schedule is correctly
@ -957,10 +957,10 @@ TEST_F(NightLightTest, TestCustomScheduleInvertedStartAndEndTimesCase1) {
EXPECT_TRUE(controller->GetEnabled());
TestCompositorsTemperature(0.4f);
EXPECT_TRUE(controller->GetIsTimerRunningForTesting());
EXPECT_TRUE(controller->timer()->IsRunning());
// NightLight should end in two hours.
EXPECT_EQ(base::TimeDelta::FromHours(2),
controller->GetCurrentTimerDelayForTesting());
controller->timer()->GetCurrentDelay());
}
// Case 2: "Now" is between "end" and "start".
@ -983,10 +983,10 @@ TEST_F(NightLightTest, TestCustomScheduleInvertedStartAndEndTimesCase2) {
EXPECT_FALSE(controller->GetEnabled());
TestCompositorsTemperature(0.0f);
EXPECT_TRUE(controller->GetIsTimerRunningForTesting());
EXPECT_TRUE(controller->timer()->IsRunning());
// NightLight should start in 15 hours.
EXPECT_EQ(base::TimeDelta::FromHours(15),
controller->GetCurrentTimerDelayForTesting());
controller->timer()->GetCurrentDelay());
}
// Case 3: "Now" is greater than both "start" and "end".
@ -1009,10 +1009,10 @@ TEST_F(NightLightTest, TestCustomScheduleInvertedStartAndEndTimesCase3) {
EXPECT_TRUE(controller->GetEnabled());
TestCompositorsTemperature(0.4f);
EXPECT_TRUE(controller->GetIsTimerRunningForTesting());
EXPECT_TRUE(controller->timer()->IsRunning());
// NightLight should end in 5 hours.
EXPECT_EQ(base::TimeDelta::FromHours(5),
controller->GetCurrentTimerDelayForTesting());
controller->timer()->GetCurrentDelay());
}
TEST_F(NightLightTest, TestAmbientLightEnabledSetting_FeatureOn) {
@ -1149,7 +1149,7 @@ TEST_F(NightLightTest, MultiUserManualStatusToggleWithSchedules) {
// expected in NightLight's status.
delegate()->SetFakeNow(test_case.fake_now);
if (user_1_previous_status != test_case.user_1_expected_status)
controller->FireTimerForTesting();
controller->timer()->FireNow();
user_1_previous_status = test_case.user_1_expected_status;
// The untoggled states for both users should match the expected ones
@ -1205,9 +1205,9 @@ TEST_F(NightLightTest, ManualStatusToggleCanPersistAfterResumeFromSuspend) {
// turn back off at 8:00 PM.
controller->Toggle();
EXPECT_TRUE(controller->GetEnabled());
EXPECT_TRUE(controller->GetIsTimerRunningForTesting());
EXPECT_TRUE(controller->timer()->IsRunning());
EXPECT_EQ(base::TimeDelta::FromHours(9),
controller->GetCurrentTimerDelayForTesting());
controller->timer()->GetCurrentDelay());
// Simulate suspend and then resume at 2:00 PM (which is outside the user's
// custom schedule). However, the manual toggle to on should be kept.
@ -1658,7 +1658,7 @@ TEST_F(AutoNightLightTest, Notification) {
// Simulate reaching sunset.
delegate()->SetFakeNow(TimeOfDay(20 * 60)); // Now is 8:00 PM.
controller->FireTimerForTesting();
controller->timer()->FireNow();
EXPECT_TRUE(controller->GetEnabled());
auto* notification = controller->GetAutoNightLightNotificationForTesting();
ASSERT_TRUE(notification);
@ -1669,13 +1669,13 @@ TEST_F(AutoNightLightTest, Notification) {
// dismissed.
notification->delegate()->Click(absl::nullopt, absl::nullopt);
controller->SetEnabled(false,
TimeSchedulerController::TimeSetterSource::kUser);
NightLightControllerImpl::AnimationDuration::kShort);
EXPECT_FALSE(controller->GetEnabled());
EXPECT_FALSE(controller->GetAutoNightLightNotificationForTesting());
// Simulate reaching next sunset. The notification should no longer show.
delegate()->SetFakeNow(TimeOfDay(20 * 60)); // Now is 8:00 PM.
controller->FireTimerForTesting();
controller->timer()->FireNow();
EXPECT_TRUE(controller->GetEnabled());
EXPECT_FALSE(controller->GetAutoNightLightNotificationForTesting());
}
@ -1687,12 +1687,12 @@ TEST_F(AutoNightLightTest, DismissNotificationOnTurningOff) {
controller->GetScheduleType());
// Use a fake geoposition with sunset/sunrise times at 5pm/3am.
controller->SetCurrentGeoposition(
SimpleGeoposition{kFakePosition2_Latitude, kFakePosition2_Longitude});
controller->SetCurrentGeoposition(NightLightController::SimpleGeoposition{
kFakePosition2_Latitude, kFakePosition2_Longitude});
// Simulate reaching sunset.
delegate()->SetFakeNow(TimeOfDay(17 * 60)); // Now is 5:00 PM.
controller->FireTimerForTesting();
controller->timer()->FireNow();
EXPECT_TRUE(controller->GetEnabled());
auto* notification = controller->GetAutoNightLightNotificationForTesting();
ASSERT_TRUE(notification);
@ -1702,15 +1702,15 @@ TEST_F(AutoNightLightTest, DismissNotificationOnTurningOff) {
// 8pm/4am, so now is before sunset. Night Light should turn off, and the
// stale notification from above should be removed. However, its removal
// should not affect kAutoNightLightNotificationDismissed.
controller->SetCurrentGeoposition(
SimpleGeoposition{kFakePosition1_Latitude, kFakePosition1_Longitude});
controller->SetCurrentGeoposition(NightLightController::SimpleGeoposition{
kFakePosition1_Latitude, kFakePosition1_Longitude});
EXPECT_FALSE(controller->GetEnabled());
EXPECT_FALSE(controller->GetAutoNightLightNotificationForTesting());
// Simulate reaching next sunset. The notification should still show, since it
// was never dismissed by the user.
delegate()->SetFakeNow(TimeOfDay(20 * 60)); // Now is 8:00 PM.
controller->FireTimerForTesting();
controller->timer()->FireNow();
EXPECT_TRUE(controller->GetEnabled());
EXPECT_TRUE(controller->GetAutoNightLightNotificationForTesting());
}
@ -1721,7 +1721,7 @@ TEST_F(AutoNightLightTest, CannotDisableNotificationWhenSessionIsBlocked) {
// Simulate reaching sunset.
NightLightControllerImpl* controller = GetController();
delegate()->SetFakeNow(TimeOfDay(20 * 60)); // Now is 8:00 PM.
controller->FireTimerForTesting();
controller->timer()->FireNow();
EXPECT_TRUE(controller->GetEnabled());
auto* notification = controller->GetAutoNightLightNotificationForTesting();
ASSERT_TRUE(notification);
@ -1741,7 +1741,7 @@ TEST_F(AutoNightLightTest, OverriddenByUser) {
// Simulate reaching sunset.
delegate()->SetFakeNow(TimeOfDay(20 * 60)); // Now is 8:00 PM.
controller->FireTimerForTesting();
controller->timer()->FireNow();
EXPECT_TRUE(controller->GetEnabled());
EXPECT_FALSE(controller->GetAutoNightLightNotificationForTesting());
}

@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "ash/system/time/time_of_day.h"
#include "ash/system/night_light/time_of_day.h"
#include "base/i18n/time_formatting.h"
#include "base/strings/utf_string_conversions.h"

@ -2,8 +2,8 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef ASH_SYSTEM_TIME_TIME_OF_DAY_H_
#define ASH_SYSTEM_TIME_TIME_OF_DAY_H_
#ifndef ASH_SYSTEM_NIGHT_LIGHT_TIME_OF_DAY_H_
#define ASH_SYSTEM_NIGHT_LIGHT_TIME_OF_DAY_H_
#include <string>
@ -49,4 +49,4 @@ class ASH_EXPORT TimeOfDay {
} // namespace ash
#endif // ASH_SYSTEM_TIME_TIME_OF_DAY_H_
#endif // ASH_SYSTEM_NIGHT_LIGHT_TIME_OF_DAY_H_

@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "ash/system/time/time_of_day.h"
#include "ash/system/night_light/time_of_day.h"
#include "base/i18n/rtl.h"
#include "base/test/icu_test_util.h"

@ -1,342 +0,0 @@
// Copyright 2021 The Chromium 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 "ash/system/time/time_scheduler_controller.h"
#include <cmath>
#include <memory>
#include "ash/constants/ash_pref_names.h"
#include "ash/session/session_controller_impl.h"
#include "ash/shell.h"
#include "ash/strings/grit/ash_strings.h"
#include "base/bind.h"
#include "base/i18n/time_formatting.h"
#include "base/logging.h"
#include "base/numerics/ranges.h"
#include "base/time/time.h"
#include "components/prefs/pref_service.h"
#include "third_party/icu/source/i18n/astro.h"
#include "ui/aura/env.h"
namespace ash {
namespace {
class TimeSchedulerControllerDelegateImpl
: public TimeSchedulerController::Delegate {
public:
TimeSchedulerControllerDelegateImpl() = default;
~TimeSchedulerControllerDelegateImpl() override = default;
// ash::NightLightControllerImpl::Delegate:
base::Time GetNow() const override { return base::Time::Now(); }
base::Time GetSunsetTime() const override { return GetSunRiseSet(false); }
base::Time GetSunriseTime() const override { return GetSunRiseSet(true); }
bool SetGeoposition(const SimpleGeoposition& position) override {
if (geoposition_ && *geoposition_ == position)
return false;
geoposition_ = std::make_unique<SimpleGeoposition>(position);
return true;
}
bool HasGeoposition() const override { return !!geoposition_; }
private:
// Note that the below computation is intentionally performed every time
// GetSunsetTime() or GetSunriseTime() is called rather than once whenever we
// receive a geoposition (which happens at least once a day). This increases
// the chances of getting accurate values, especially around DST changes.
base::Time GetSunRiseSet(bool sunrise) const {
if (!HasGeoposition()) {
LOG(ERROR) << "Invalid geoposition. Using default time for "
<< (sunrise ? "sunrise." : "sunset.");
return sunrise
? TimeOfDay(
TimeSchedulerController::kDefaultEndTimeOffsetMinutes)
.ToTimeToday()
: TimeOfDay(
TimeSchedulerController::kDefaultStartTimeOffsetMinutes)
.ToTimeToday();
}
icu::CalendarAstronomer astro(geoposition_->longitude,
geoposition_->latitude);
// For sunset and sunrise times calculations to be correct, the time of the
// icu::CalendarAstronomer object should be set to a time near local noon.
// This avoids having the computation flopping over into an adjacent day.
// See the documentation of icu::CalendarAstronomer::getSunRiseSet().
// Note that the icu calendar works with milliseconds since epoch, and
// base::Time::FromDoubleT() / ToDoubleT() work with seconds since epoch.
const double midday_today_sec =
TimeOfDay(12 * 60).ToTimeToday().ToDoubleT();
astro.setTime(midday_today_sec * 1000.0);
const double sun_rise_set_ms = astro.getSunRiseSet(sunrise);
return base::Time::FromDoubleT(sun_rise_set_ms / 1000.0);
}
std::unique_ptr<SimpleGeoposition> geoposition_;
};
} // namespace
TimeSchedulerController::TimeSchedulerController() {}
TimeSchedulerController::TimeSchedulerController(
const std::string prefs_path_enabled,
const std::string prefs_path_latitude,
const std::string prefs_path_longitude,
base::RepeatingCallback<void(bool, TimeSetterSource)> set_enabled_callback,
base::RepeatingCallback<void()> refresh_state_callback)
: prefs_path_enabled_(prefs_path_enabled),
prefs_path_latitude_(prefs_path_latitude),
prefs_path_longitude_(prefs_path_longitude),
set_enabled_callback_(std::move(set_enabled_callback)),
refresh_state_callback_(std::move(refresh_state_callback)),
delegate_(std::make_unique<TimeSchedulerControllerDelegateImpl>()) {}
TimeSchedulerController::~TimeSchedulerController() {}
void TimeSchedulerController::RefreshScheduleTimer(
PrefService* active_user_pref_service,
base::Time start_time,
base::Time end_time,
bool did_schedule_change,
bool keep_manual_toggles_during_schedules) {
bool is_enabled = GetEnabled(active_user_pref_service);
if (keep_manual_toggles_during_schedules &&
MaybeRestoreSchedule(active_user_pref_service)) {
refresh_state_callback_.Run();
return;
}
// NOTE: Users can set any weird combinations.
const base::Time now = delegate()->GetNow();
if (end_time <= start_time) {
// Example:
// Start: 9:00 PM, End: 6:00 AM.
//
// 6:00 21:00
// <----- + ------------------ + ----->
// | |
// end start
//
// Note that the above times are times of day (today). It is important to
// know where "now" is with respect to these times to decide how to adjust
// them.
if (end_time >= now) {
// If the end time (today) is greater than the time now, this means "now"
// is within the NightLight schedule, and the start time is actually
// yesterday. The above timeline is interpreted as:
//
// 21:00 (-1day) 6:00
// <----- + ----------- + ------ + ----->
// | | |
// start now end
//
start_time -= base::TimeDelta::FromDays(1);
} else {
// Two possibilities here:
// - Either "now" is greater than the end time, but less than start time.
// This means NightLight is outside the schedule, waiting for the next
// start time. The end time is actually a day later.
// - Or "now" is greater than both the start and end times. This means
// NightLight is within the schedule, waiting to turn off at the next
// end time, which is also a day later.
end_time += base::TimeDelta::FromDays(1);
}
}
DCHECK_GE(end_time, start_time);
// The target status that we need to set NightLight to now if a change of
// status is needed immediately.
bool enable_now = false;
// Where are we now with respect to the start and end times?
if (now < start_time) {
// Example:
// Start: 6:00 PM today, End: 6:00 AM tomorrow, Now: 4:00 PM.
//
// <----- + ----------- + ----------- + ----->
// | | |
// now start end
//
// In this case, we need to disable NightLight immediately if it's enabled.
enable_now = false;
} else if (now >= start_time && now < end_time) {
// Example:
// Start: 6:00 PM today, End: 6:00 AM tomorrow, Now: 11:00 PM.
//
// <----- + ----------- + ----------- + ----->
// | | |
// start now end
//
// Start NightLight right away. Our future start time is a day later than
// its current value.
enable_now = true;
start_time += base::TimeDelta::FromDays(1);
} else { // now >= end_time.
// Example:
// Start: 6:00 PM today, End: 10:00 PM today, Now: 11:00 PM.
//
// <----- + ----------- + ----------- + ----->
// | | |
// start end now
//
// In this case, our future start and end times are a day later from their
// current values. NightLight needs to be ended immediately if it's already
// enabled.
enable_now = false;
start_time += base::TimeDelta::FromDays(1);
end_time += base::TimeDelta::FromDays(1);
}
// After the above processing, the start and end time are all in the future.
DCHECK_GE(start_time, now);
DCHECK_GE(end_time, now);
if (did_schedule_change && enable_now != is_enabled) {
// If the change in the schedule introduces a change in the status, then
// calling SetEnabled() is all we need, since it will trigger a change in
// the user prefs to which we will respond by calling Refresh(). This will
// end up in this function again, adjusting all the needed schedules.
set_enabled_callback_.Run(enable_now, TimeSetterSource::kUser);
return;
}
// We reach here in one of the following conditions:
// 1) If schedule changes don't result in changes in the status, we need to
// explicitly update the timer to re-schedule the next toggle to account for
// any changes.
// 2) The user has just manually toggled the status of NightLight either from
// the System Menu or System Settings. In this case, we respect the user
// wish and maintain the current status that they desire, but we schedule the
// status to be toggled according to the time that corresponds with the
// opposite status of the current one.
ScheduleNextToggle(active_user_pref_service,
is_enabled ? end_time - now : start_time - now,
is_enabled);
refresh_state_callback_.Run();
}
void TimeSchedulerController::ScheduleNextToggle(
PrefService* active_user_pref_service,
base::TimeDelta delay,
bool old_status) {
const bool new_status = !old_status;
const base::Time target_time = delegate()->GetNow() + delay;
per_user_schedule_target_state_[active_user_pref_service] =
ScheduleTargetState{target_time, new_status};
VLOG(1) << "Setting Night Light to toggle to "
<< (new_status ? "enabled" : "disabled") << " at "
<< base::TimeFormatTimeOfDay(target_time);
timer_.Start(FROM_HERE, delay,
base::BindRepeating(set_enabled_callback_, new_status,
TimeSetterSource::kAutomatic));
}
void TimeSchedulerController::StopTimer() {
timer_.Stop();
}
void TimeSchedulerController::LoadCachedGeopositionIfNeeded(
PrefService* active_user_pref_service) {
DCHECK(active_user_pref_service);
// Even if there is a geoposition, but it's coming from a previously cached
// value, switching users should load the currently saved values for the
// new user. This is to keep users' prefs completely separate. We only ignore
// the cached values once we have a valid non-cached geoposition from any
// user in the same session.
if (delegate_->HasGeoposition() && !is_current_geoposition_from_cache_)
return;
if (!active_user_pref_service->HasPrefPath(prefs_path_latitude_) ||
!active_user_pref_service->HasPrefPath(prefs_path_longitude_)) {
VLOG(1) << "No valid current geoposition and no valid cached geoposition"
" are available. Will use default times for sunset / sunrise.";
return;
}
VLOG(1) << "Temporarily using a previously cached geoposition.";
delegate_->SetGeoposition(SimpleGeoposition{
active_user_pref_service->GetDouble(prefs_path_latitude_),
active_user_pref_service->GetDouble(prefs_path_longitude_)});
is_current_geoposition_from_cache_ = true;
}
void TimeSchedulerController::StoreCachedGeoposition(
const SimpleGeoposition& position) {
is_current_geoposition_from_cache_ = false;
const SessionControllerImpl* session_controller =
Shell::Get()->session_controller();
for (const auto& user_session : session_controller->GetUserSessions()) {
PrefService* pref_service = session_controller->GetUserPrefServiceForUser(
user_session->user_info.account_id);
if (!pref_service)
continue;
pref_service->SetDouble(prefs_path_latitude_, position.latitude);
pref_service->SetDouble(prefs_path_longitude_, position.longitude);
}
}
bool TimeSchedulerController::MaybeRestoreSchedule(
PrefService* active_user_pref_service) {
bool is_enabled = GetEnabled(active_user_pref_service);
auto iter = per_user_schedule_target_state_.find(active_user_pref_service);
if (iter == per_user_schedule_target_state_.end())
return false;
ScheduleTargetState& target_state = iter->second;
// It may be that the device was suspended for a very long time that the
// target time is no longer valid.
if (target_state.target_time <= delegate_->GetNow())
return false;
VLOG(1) << "Restoring a previous schedule.";
DCHECK_NE(is_enabled, target_state.target_status);
ScheduleNextToggle(active_user_pref_service,
target_state.target_time - delegate_->GetNow(),
is_enabled);
return true;
}
bool TimeSchedulerController::GetEnabled(
PrefService* active_user_pref_service) const {
return active_user_pref_service &&
active_user_pref_service->GetBoolean(prefs_path_enabled_);
}
base::Time TimeSchedulerController::GetSunRiseSet(bool sunrise) const {
if (!delegate_->HasGeoposition()) {
LOG(ERROR) << "Invalid geoposition. Using default time for "
<< (sunrise ? "sunrise." : "sunset.");
return sunrise
? TimeOfDay(
TimeSchedulerController::kDefaultEndTimeOffsetMinutes)
.ToTimeToday()
: TimeOfDay(
TimeSchedulerController::kDefaultStartTimeOffsetMinutes)
.ToTimeToday();
}
icu::CalendarAstronomer astro(geoposition_->longitude,
geoposition_->latitude);
// For sunset and sunrise times calculations to be correct, the time of the
// icu::CalendarAstronomer object should be set to a time near local noon.
// This avoids having the computation flopping over into an adjacent day.
// See the documentation of icu::CalendarAstronomer::getSunRiseSet().
// Note that the icu calendar works with milliseconds since epoch, and
// base::Time::FromDoubleT() / ToDoubleT() work with seconds since epoch.
const double midday_today_sec = TimeOfDay(12 * 60).ToTimeToday().ToDoubleT();
astro.setTime(midday_today_sec * 1000.0);
const double sun_rise_set_ms = astro.getSunRiseSet(sunrise);
return base::Time::FromDoubleT(sun_rise_set_ms / 1000.0);
}
} // namespace ash

@ -1,171 +0,0 @@
// Copyright 2021 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef ASH_SYSTEM_TIME_TIME_SCHEDULER_CONTROLLER_H_
#define ASH_SYSTEM_TIME_TIME_SCHEDULER_CONTROLLER_H_
#include <memory>
#include "ash/ash_export.h"
#include "ash/public/cpp/simple_geo_position.h"
#include "ash/system/time/time_of_day.h"
#include "base/containers/flat_map.h"
#include "base/time/time.h"
#include "base/timer/timer.h"
#include "components/prefs/pref_change_registrar.h"
#include "third_party/icu/source/i18n/astro.h"
class PrefService;
namespace ash {
// Schedule a timer to enable and disable a mode specified in a user pref
// `prefs_path_enabled`. This class also enforces a delegate which requires
// time zone, sunrise, and sunset time.
class ASH_EXPORT TimeSchedulerController {
public:
// This class enables us to inject fake values for "Now" as well as the sunset
// and sunrise times, so that we can reliably test the behavior in various
// schedule types and times.
class Delegate {
public:
// TimeSchedulerController owns the delegate.
virtual ~Delegate() = default;
// Gets the current time.
virtual base::Time GetNow() const = 0;
// Gets the sunset and sunrise times.
virtual base::Time GetSunsetTime() const = 0;
virtual base::Time GetSunriseTime() const = 0;
// Provides the delegate with the geoposition so that it can be used to
// calculate sunset and sunrise times.
// Returns true if |position| is different than the current known value,
// potentially requiring a refresh of the schedule. False otherwise.
virtual bool SetGeoposition(const SimpleGeoposition& position) = 0;
// Returns true if a geoposition value is available.
virtual bool HasGeoposition() const = 0;
};
enum class TimeSetterSource {
// Short animation (2 seconds) used for manual changes of the status
// by the user.
kUser,
// Long animation (20 seconds) used for applying the color temperature
// gradually as a result of getting into or out of the automatically
// scheduled the mode. This gives the user a smooth transition.
kAutomatic,
};
static const int kDefaultStartTimeOffsetMinutes = 18 * 60;
static const int kDefaultEndTimeOffsetMinutes = 6 * 60;
TimeSchedulerController();
TimeSchedulerController(
const std::string prefs_path_enabled,
const std::string prefs_path_latitude,
const std::string prefs_path_longitude,
base::RepeatingCallback<void(bool, TimeSetterSource)>
set_enabled_callback,
base::RepeatingCallback<void()> refresh_state_callback);
~TimeSchedulerController();
base::OneShotTimer* timer() { return &timer_; }
bool is_current_geoposition_from_cache() const {
return is_current_geoposition_from_cache_;
}
Delegate* delegate() { return delegate_.get(); }
// Given the desired start and end times that determine the time interval
// during which the mode will be ON, depending on the time of "now", it
// refreshes the |timer_| to either schedule the future start or end of
// the mode, as well as update the current status if needed.
// For |did_schedule_change| and |keep_manual_toggles_during_schedules|, see
// Refresh() above.
// This function should never be called if the schedule type is |kNone|.
void RefreshScheduleTimer(PrefService* active_user_pref_service,
base::Time start_time,
base::Time end_time,
bool did_schedule_change,
bool keep_manual_toggles_during_schedules);
// Schedule the upcoming next toggle of the mode. This is used for the
// automatic status changes of the mode which always use an
// AnimationDurationType::kLong.
void ScheduleNextToggle(PrefService* active_user_pref_service,
base::TimeDelta delay,
bool old_status);
void StopTimer();
// Called only when the active user changes in order to see if we need to use
// a previously cached geoposition value from the active user's prefs.
void LoadCachedGeopositionIfNeeded(PrefService* active_user_pref_service);
// Called whenever we receive a new geoposition update to cache it in all
// logged-in users' prefs so that it can be used later in the event of not
// being able to retrieve a valid geoposition.
void StoreCachedGeoposition(const SimpleGeoposition& position);
// Attempts restoring a previously stored schedule for the active user when
// the current status is `is_enabled` if possible and returns true if so,
// false otherwise.
bool MaybeRestoreSchedule(PrefService* active_user_pref_service);
void SetDelegateForTesting(std::unique_ptr<Delegate> delegate) {
delegate_ = std::move(delegate);
}
private:
// Return true if user pref `prefs_path_enabled_` indicates the enabled mode.
bool GetEnabled(PrefService* active_user_pref_service) const;
// Note that the below computation is intentionally performed every time
// GetSunsetTime() or GetSunriseTime() is called rather than once whenever we
// receive a geoposition (which happens at least once a day). This increases
// the chances of getting accurate values, especially around DST changes.
base::Time GetSunRiseSet(bool sunrise) const;
// Tracks the upcoming state changes per each user due to automatic
// schedules. This can be used to restore a manually toggled status while the
// schedule is being used. See MaybeRestoreSchedule().
struct ScheduleTargetState {
// The time at which the mode will switch to |target_status| defined
// below.
base::Time target_time;
bool target_status;
};
base::flat_map<PrefService*, ScheduleTargetState>
per_user_schedule_target_state_;
// The timer that schedules the start and end of the mode when the schedule
// type is either kSunsetToSunrise or kCustom.
base::OneShotTimer timer_;
// True if the current geoposition value used by the Delegate is from a
// previously cached value in the user prefs of any of the users in the
// current session. It is reset to false once we receive a newly-updated
// geoposition from the client.
// This is used to treat the current geoposition as temporary until we receive
// a valid geoposition update, and also not to let a cached geoposition value
// to leak to another user for privacy reasons.
bool is_current_geoposition_from_cache_ = false;
const std::string prefs_path_enabled_;
const std::string prefs_path_latitude_;
const std::string prefs_path_longitude_;
base::RepeatingCallback<void(bool, TimeSetterSource)> set_enabled_callback_;
base::RepeatingCallback<void()> refresh_state_callback_;
std::unique_ptr<SimpleGeoposition> geoposition_;
std::unique_ptr<TimeSchedulerController::Delegate> delegate_;
};
} // namespace ash
#endif // ASH_SYSTEM_TIME_TIME_SCHEDULER_CONTROLLER_H_

@ -151,7 +151,7 @@ base::Time NightLightClient::GetNow() const {
void NightLightClient::SendCurrentGeoposition() {
night_light_controller_->SetCurrentGeoposition(
ash::SimpleGeoposition{latitude_, longitude_});
ash::NightLightController::SimpleGeoposition{latitude_, longitude_});
}
void NightLightClient::ScheduleNextRequest(base::TimeDelta delay) {

@ -19,7 +19,7 @@
namespace {
using ScheduleType = ash::NightLightController::ScheduleType;
using SimpleGeoposition = ash::SimpleGeoposition;
using SimpleGeoposition = ash::NightLightController::SimpleGeoposition;
// Constructs a TimeZone object from the given |timezone_id|.
std::unique_ptr<icu::TimeZone> CreateTimezone(const char* timezone_id) {