| 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..2529adbd586f2b4634d4d96bb750167728abaf41 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,70 @@ 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() {
|
| + // Verify that the active user session has started to make sure that owner
|
| + // has already gone through OOBE and agreed on the terms of service, so that
|
| + // we can use the geolocation services. crbug.com/715686.
|
| + DCHECK(Shell::Get()->session_controller()->IsActiveUserSessionStarted());
|
| +
|
| + device::GeolocationProvider* geolocation_provider =
|
| + device::GeolocationProvider::GetInstance();
|
| +
|
| + // We only need the low-accuracy IP-based geolocation position, which does
|
| + // not require user consent.
|
| + geolocation_provider->UserDidOptIntoLocationServices();
|
| + geolocation_subscription_ = geolocation_provider->AddLocationUpdateCallback(
|
| + base::Bind(&NightLightControllerDelegateImpl::OnGeoPosition,
|
| + base::Unretained(this)),
|
| + 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 ? TimeOfDay(kDefaultEndTimeOffsetMinutes).ToTimeToday()
|
| + : TimeOfDay(kDefaultStartTimeOffsetMinutes).ToTimeToday();
|
| + }
|
| +
|
| + 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|.
|
| @@ -61,6 +120,12 @@ void NightLightController::RegisterPrefs(PrefRegistrySimple* registry) {
|
| registry->RegisterBooleanPref(prefs::kNightLightEnabled, false);
|
| registry->RegisterDoublePref(prefs::kNightLightTemperature,
|
| kDefaultColorTemperature);
|
| + registry->RegisterIntegerPref(prefs::kNightLightScheduleType,
|
| + static_cast<int>(ScheduleType::kNone));
|
| + registry->RegisterIntegerPref(prefs::kNightLightCustomStartTime,
|
| + kDefaultStartTimeOffsetMinutes);
|
| + registry->RegisterIntegerPref(prefs::kNightLightCustomEndTime,
|
| + kDefaultEndTimeOffsetMinutes);
|
| }
|
|
|
| void NightLightController::AddObserver(Observer* observer) {
|
| @@ -83,9 +148,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::kNone;
|
| +}
|
| +
|
| +TimeOfDay NightLightController::GetCustomStartTime() const {
|
| + if (active_user_pref_service_) {
|
| + return TimeOfDay(active_user_pref_service_->GetInteger(
|
| + prefs::kNightLightCustomStartTime));
|
| + }
|
| +
|
| + return TimeOfDay(kDefaultStartTimeOffsetMinutes);
|
| +}
|
| +
|
| +TimeOfDay NightLightController::GetCustomEndTime() const {
|
| + if (active_user_pref_service_) {
|
| + return TimeOfDay(
|
| + active_user_pref_service_->GetInteger(prefs::kNightLightCustomEndTime));
|
| + }
|
| +
|
| + return TimeOfDay(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 +193,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(TimeOfDay start_time) {
|
| + if (active_user_pref_service_) {
|
| + active_user_pref_service_->SetInteger(
|
| + prefs::kNightLightCustomStartTime,
|
| + start_time.offset_minutes_from_zero_hour());
|
| + }
|
| +}
|
| +
|
| +void NightLightController::SetCustomEndTime(TimeOfDay 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(
|
| @@ -106,15 +225,29 @@ void NightLightController::OnActiveUserSessionChanged(
|
| // Initial login and user switching in multi profiles.
|
| pref_change_registrar_.reset();
|
| active_user_pref_service_ = Shell::Get()->GetActiveUserPrefService();
|
| + delegate_ = base::MakeUnique<NightLightControllerDelegateImpl>();
|
| InitFromUserPrefs();
|
| }
|
|
|
| -void NightLightController::Refresh() {
|
| +void NightLightController::SetDelegateForTesting(
|
| + 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.
|
| + last_animation_type_ = animation_type_;
|
| + animation_type_ = AnimationDurationType::kShort;
|
| }
|
|
|
| void NightLightController::StartWatchingPrefsChanges() {
|
| @@ -130,6 +263,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 +284,7 @@ void NightLightController::InitFromUserPrefs() {
|
| }
|
|
|
| StartWatchingPrefsChanges();
|
| - Refresh();
|
| + Refresh(true /* did_schedule_change */);
|
| NotifyStatusChanged();
|
| }
|
|
|
| @@ -150,13 +295,141 @@ 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::kNone:
|
| + timer_.Stop();
|
| + return;
|
| +
|
| + case ScheduleType::kSunsetToSunrise:
|
| + RefreshScheduleTimer(delegate_->GetSunsetTime(),
|
| + delegate_->GetSunriseTime(), did_schedule_change);
|
| + return;
|
| +
|
| + case ScheduleType::kCustom:
|
| + RefreshScheduleTimer(GetCustomStartTime().ToTimeToday(),
|
| + GetCustomEndTime().ToTimeToday(),
|
| + did_schedule_change);
|
| + return;
|
| + }
|
| +}
|
| +
|
| +void NightLightController::RefreshScheduleTimer(base::Time start_time,
|
| + base::Time end_time,
|
| + bool did_schedule_change) {
|
| + if (GetScheduleType() == ScheduleType::kNone) {
|
| + NOTREACHED();
|
| + timer_.Stop();
|
| + return;
|
| + }
|
| +
|
| + // 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 enable_now = false;
|
| +
|
| + // 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: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, 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(), AnimationDurationType::kLong));
|
| }
|
|
|
| } // namespace ash
|
|
|