Chromium Code Reviews| Index: ash/system/night_light/night_light_controller.cc |
| diff --git a/ash/system/night_light/night_light_controller.cc b/ash/system/night_light/night_light_controller.cc |
| index 3a28ba0807acf030742867b1ea86e2881a6f2629..12e2a3c8669aa8b5bc3aa69a26fb44f17d851d47 100644 |
| --- a/ash/system/night_light/night_light_controller.cc |
| +++ b/ash/system/night_light/night_light_controller.cc |
| @@ -10,6 +10,9 @@ |
| #include "base/time/time.h" |
| #include "components/prefs/pref_registry_simple.h" |
| #include "components/prefs/pref_service.h" |
| +#include "device/geolocation/geolocation_provider.h" |
| +#include "device/geolocation/geoposition.h" |
| +#include "third_party/icu/source/i18n/astro.h" |
| #include "ui/compositor/layer.h" |
| #include "ui/compositor/scoped_layer_animation_settings.h" |
| @@ -17,14 +20,62 @@ namespace ash { |
| namespace { |
| +// Default start time at 6:00 PM as an offset from 00:00. |
| +const int kDefaultStartTimeOffsetMinutes = 18 * 60; |
| + |
| +// Default end time at 6:00 AM as an offset from 00:00. |
| +const int kDefaultEndTimeOffsetMinutes = 6 * 60; |
| + |
| constexpr float kDefaultColorTemperature = 0.5f; |
| -// The duration of the temperature change animation when the change is a result |
| -// of a manual user setting. |
| -// TODO(afakhry): Add automatic schedule animation duration when you implement |
| -// that part. It should be large enough (20 seconds as agreed) to give the user |
| -// a nice smooth transition. |
| -constexpr int kManualToggleAnimationDurationSec = 2; |
| +// The duration of the temperature change animation for |
| +// AnimationDurationType::kShort. |
| +constexpr int kManualAnimationDurationSec = 2; |
| + |
| +// The duration of the temperature change animation for |
| +// AnimationDurationType::kLong. |
| +constexpr int kAutomaticAnimationDurationSec = 20; |
| + |
| +class NightLightControllerDelegateImpl : public NightLightController::Delegate { |
| + public: |
| + NightLightControllerDelegateImpl() { |
| + device::GeolocationProvider::GetInstance() |
| + ->UserDidOptIntoLocationServices(); |
| + geolocation_subscription_ = |
| + device::GeolocationProvider::GetInstance()->AddLocationUpdateCallback( |
|
James Cook
2017/05/23 16:34:58
optional: "using device::GeolocationProvider" at g
afakhry
2017/05/24 04:21:10
Done.
I also moved the creation of the delegate t
|
| + base::Bind(&NightLightControllerDelegateImpl::OnGeoPosition, |
| + base::Unretained(this)), |
|
James Cook
2017/05/23 16:34:58
Double-checking: Does |this| outlive GeolocationPr
afakhry
2017/05/24 04:21:10
I don't think so. GeolocationProviderImpl is a sin
|
| + false /* enable_high_accuracy */); |
| + } |
| + ~NightLightControllerDelegateImpl() override = default; |
| + |
| + // ash::NightLightController::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); } |
| + |
| + private: |
| + base::Time GetSunRiseSet(bool sunrise) const { |
| + if (!position_.Validate()) { |
| + return sunrise ? NightLightTime(kDefaultEndTimeOffsetMinutes).ToTime() |
| + : NightLightTime(kDefaultStartTimeOffsetMinutes).ToTime(); |
| + } |
| + |
| + icu::CalendarAstronomer astro(position_.longitude, position_.latitude); |
| + // Note that the icu calendar return milliseconds since epoch, and |
| + // base::Time::FromDoubleT() expects seconds. |
| + return base::Time::FromDoubleT(astro.getSunRiseSet(sunrise) / 1000.0); |
| + } |
| + |
| + void OnGeoPosition(const device::Geoposition& pos) { position_ = pos; } |
| + |
| + using LocationSubscription = device::GeolocationProvider::Subscription; |
| + std::unique_ptr<LocationSubscription> geolocation_subscription_; |
| + |
| + device::Geoposition position_; |
| + |
| + DISALLOW_COPY_AND_ASSIGN(NightLightControllerDelegateImpl); |
| +}; |
| // Applies the given |layer_temperature| to all the layers of the root windows |
| // with the given |animation_duration|. |
| @@ -48,7 +99,8 @@ void ApplyColorTemperatureToLayers(float layer_temperature, |
| NightLightController::NightLightController( |
| SessionController* session_controller) |
| - : session_controller_(session_controller) { |
| + : session_controller_(session_controller), |
| + delegate_(base::MakeUnique<NightLightControllerDelegateImpl>()) { |
| session_controller_->AddObserver(this); |
| } |
| @@ -61,6 +113,12 @@ void NightLightController::RegisterPrefs(PrefRegistrySimple* registry) { |
| registry->RegisterBooleanPref(prefs::kNightLightEnabled, false); |
| registry->RegisterDoublePref(prefs::kNightLightTemperature, |
| kDefaultColorTemperature); |
| + registry->RegisterIntegerPref(prefs::kNightLightScheduleType, |
| + static_cast<int>(ScheduleType::kNever)); |
| + registry->RegisterIntegerPref(prefs::kNightLightCustomStartTime, |
| + kDefaultStartTimeOffsetMinutes); |
| + registry->RegisterIntegerPref(prefs::kNightLightCustomEndTime, |
| + kDefaultEndTimeOffsetMinutes); |
| } |
| void NightLightController::AddObserver(Observer* observer) { |
| @@ -83,9 +141,40 @@ float NightLightController::GetColorTemperature() const { |
| return kDefaultColorTemperature; |
| } |
| -void NightLightController::SetEnabled(bool enabled) { |
| - if (active_user_pref_service_) |
| +NightLightController::ScheduleType NightLightController::GetScheduleType() |
| + const { |
| + if (active_user_pref_service_) { |
| + return static_cast<ScheduleType>( |
| + active_user_pref_service_->GetInteger(prefs::kNightLightScheduleType)); |
| + } |
| + |
| + return ScheduleType::kNever; |
| +} |
| + |
| +NightLightTime NightLightController::GetCustomStartTime() const { |
| + if (active_user_pref_service_) { |
| + return NightLightTime(active_user_pref_service_->GetInteger( |
| + prefs::kNightLightCustomStartTime)); |
| + } |
| + |
| + return NightLightTime(kDefaultStartTimeOffsetMinutes); |
| +} |
| + |
| +NightLightTime NightLightController::GetCustomEndTime() const { |
| + if (active_user_pref_service_) { |
| + return NightLightTime( |
| + active_user_pref_service_->GetInteger(prefs::kNightLightCustomEndTime)); |
| + } |
| + |
| + return NightLightTime(kDefaultEndTimeOffsetMinutes); |
| +} |
| + |
| +void NightLightController::SetEnabled(bool enabled, |
| + AnimationDurationType animation_type) { |
| + if (active_user_pref_service_) { |
| + animation_type_ = animation_type; |
| active_user_pref_service_->SetBoolean(prefs::kNightLightEnabled, enabled); |
| + } |
| } |
| void NightLightController::SetColorTemperature(float temperature) { |
| @@ -97,8 +186,31 @@ void NightLightController::SetColorTemperature(float temperature) { |
| } |
| } |
| +void NightLightController::SetScheduleType(ScheduleType type) { |
| + if (active_user_pref_service_) { |
| + active_user_pref_service_->SetInteger(prefs::kNightLightScheduleType, |
| + static_cast<int>(type)); |
| + } |
| +} |
| + |
| +void NightLightController::SetCustomStartTime(NightLightTime start_time) { |
| + if (active_user_pref_service_) { |
| + active_user_pref_service_->SetInteger( |
| + prefs::kNightLightCustomStartTime, |
| + start_time.offset_minutes_from_zero_hour()); |
| + } |
| +} |
| + |
| +void NightLightController::SetCustomEndTime(NightLightTime end_time) { |
| + if (active_user_pref_service_) { |
| + active_user_pref_service_->SetInteger( |
| + prefs::kNightLightCustomEndTime, |
| + end_time.offset_minutes_from_zero_hour()); |
| + } |
| +} |
| + |
| void NightLightController::Toggle() { |
| - SetEnabled(!GetEnabled()); |
| + SetEnabled(!GetEnabled(), AnimationDurationType::kShort); |
| } |
| void NightLightController::OnActiveUserSessionChanged( |
| @@ -109,12 +221,25 @@ void NightLightController::OnActiveUserSessionChanged( |
| InitFromUserPrefs(); |
| } |
| -void NightLightController::Refresh() { |
| +void NightLightController::OverrideDelegateForTesting( |
| + std::unique_ptr<Delegate> delegate) { |
| + delegate_ = std::move(delegate); |
| +} |
| + |
| +void NightLightController::RefreshLayersTemperature() { |
| // TODO(afakhry): Add here refreshing of start and end times, when you |
| // implement the automatic schedule settings. |
| ApplyColorTemperatureToLayers( |
| GetEnabled() ? GetColorTemperature() : 0.0f, |
| - base::TimeDelta::FromSeconds(kManualToggleAnimationDurationSec)); |
| + base::TimeDelta::FromSeconds(animation_type_ == |
| + AnimationDurationType::kShort |
| + ? kManualAnimationDurationSec |
| + : kAutomaticAnimationDurationSec)); |
| + |
| + // Reset the animation type back to manual to consume any automatically set |
| + // animations. |
| + consumed_animation_type_ = animation_type_; |
| + animation_type_ = AnimationDurationType::kShort; |
| } |
| void NightLightController::StartWatchingPrefsChanges() { |
| @@ -130,6 +255,18 @@ void NightLightController::StartWatchingPrefsChanges() { |
| prefs::kNightLightTemperature, |
| base::Bind(&NightLightController::OnColorTemperaturePrefChanged, |
| base::Unretained(this))); |
| + pref_change_registrar_->Add( |
| + prefs::kNightLightScheduleType, |
| + base::Bind(&NightLightController::OnScheduleParamsPrefsChanged, |
| + base::Unretained(this))); |
| + pref_change_registrar_->Add( |
| + prefs::kNightLightCustomStartTime, |
| + base::Bind(&NightLightController::OnScheduleParamsPrefsChanged, |
| + base::Unretained(this))); |
| + pref_change_registrar_->Add( |
| + prefs::kNightLightCustomEndTime, |
| + base::Bind(&NightLightController::OnScheduleParamsPrefsChanged, |
| + base::Unretained(this))); |
| } |
| void NightLightController::InitFromUserPrefs() { |
| @@ -139,7 +276,7 @@ void NightLightController::InitFromUserPrefs() { |
| } |
| StartWatchingPrefsChanges(); |
| - Refresh(); |
| + Refresh(true /* did_schedule_change */); |
| NotifyStatusChanged(); |
| } |
| @@ -150,13 +287,136 @@ void NightLightController::NotifyStatusChanged() { |
| void NightLightController::OnEnabledPrefChanged() { |
| DCHECK(active_user_pref_service_); |
| - Refresh(); |
| + Refresh(false /* did_schedule_change */); |
| NotifyStatusChanged(); |
| } |
| void NightLightController::OnColorTemperaturePrefChanged() { |
| DCHECK(active_user_pref_service_); |
| - Refresh(); |
| + RefreshLayersTemperature(); |
| +} |
| + |
| +void NightLightController::OnScheduleParamsPrefsChanged() { |
| + DCHECK(active_user_pref_service_); |
| + Refresh(true /* did_schedule_change */); |
| +} |
| + |
| +void NightLightController::Refresh(bool did_schedule_change) { |
| + RefreshLayersTemperature(); |
| + |
| + const ScheduleType type = GetScheduleType(); |
| + switch (type) { |
| + case ScheduleType::kNever: |
| + timer_.Stop(); |
| + return; |
| + |
| + case ScheduleType::kSunsetToSunrise: |
| + RefreshScheduleTimer(delegate_->GetSunsetTime(), |
| + delegate_->GetSunriseTime(), did_schedule_change); |
| + return; |
| + |
| + case ScheduleType::kCustom: |
| + RefreshScheduleTimer(GetCustomStartTime().ToTime(), |
| + GetCustomEndTime().ToTime(), did_schedule_change); |
| + return; |
| + } |
| +} |
| + |
| +void NightLightController::RefreshScheduleTimer(base::Time start_time, |
| + base::Time end_time, |
| + bool did_schedule_change) { |
| + DCHECK_NE(ScheduleType::kNever, GetScheduleType()); |
| + |
| + // NOTE: Users can set any weird combinations. |
| + if (end_time <= start_time) { |
| + // Example: |
| + // Start: 9:00 PM, End: 6:00 AM. |
| + // |
| + // 6:00 21:00 |
| + // <----- + ------------------ + -----> |
| + // | | |
| + // end start |
| + // |
| + // From our perspective, the end time is always 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 immediate_target_status = false; |
|
James Cook
2017/05/23 16:34:58
optional: |should_enable|? |enable_now|?
afakhry
2017/05/24 04:21:09
Done.
|
| + |
| + // Where are we now with respect to the start and end times? |
| + const base::Time now = delegate_->GetNow(); |
| + if (now < start_time) { |
| + // Example: |
| + // Start: 6:00 PM today, End: 6:00 AM tomorrow, Now: 4:00PM. |
|
James Cook
2017/05/23 16:34:58
super-nit: "4:00 PM" with space, be consistent thr
afakhry
2017/05/24 04:21:10
Done.
|
| + // |
| + // <----- + ----------- + ----------- + -----> |
| + // | | | |
| + // now start end |
| + // |
| + // In this case, we need to disable NightLight immediately if it's enabled. |
| + immediate_target_status = false; |
| + } else if (now >= start_time && now < end_time) { |
| + // Example: |
| + // Start: 6:00 PM today, End: 6:00 AM tomorrow, Now: 11:00PM. |
| + // |
| + // <----- + ----------- + ----------- + -----> |
| + // | | | |
| + // start now end |
| + // |
| + // Start NightLight right away. Our future start time is a day later than |
| + // its current value. |
| + immediate_target_status = true; |
| + start_time += base::TimeDelta::FromDays(1); |
| + } else { // now >= end_time. |
| + // Example: |
| + // Start: 6:00 PM today, End: 10:00 PM today, Now: 11:00PM. |
| + // |
| + // <----- + ----------- + ----------- + -----> |
| + // | | | |
| + // 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. |
| + immediate_target_status = 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); |
|
James Cook
2017/05/23 16:34:58
I find these DCHECKs very helpful, thanks.
afakhry
2017/05/24 04:21:10
Acknowledged.
|
| + |
| + if (did_schedule_change && immediate_target_status != 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(immediate_target_status, AnimationDurationType::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 he desires, 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); |
| +} |
| + |
| +void NightLightController::ScheduleNextToggle(base::TimeDelta delay) { |
| + timer_.Start( |
| + FROM_HERE, delay, |
| + base::Bind(&NightLightController::SetEnabled, base::Unretained(this), |
| + GetEnabled() ? false : true, AnimationDurationType::kLong)); |
|
James Cook
2017/05/23 16:34:58
nit: !GetEnabled()
afakhry
2017/05/24 04:21:10
Done.
|
| } |
| } // namespace ash |