Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(268)

Side by Side Diff: ash/system/night_light/night_light_controller.cc

Issue 2887913004: [Night Light] CL4: Automatic schedule backend. (Closed)
Patch Set: Working Created 3 years, 7 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
1 // Copyright 2017 The Chromium Authors. All rights reserved. 1 // Copyright 2017 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 #include "ash/system/night_light/night_light_controller.h" 5 #include "ash/system/night_light/night_light_controller.h"
6 6
7 #include "ash/public/cpp/ash_pref_names.h" 7 #include "ash/public/cpp/ash_pref_names.h"
8 #include "ash/session/session_controller.h" 8 #include "ash/session/session_controller.h"
9 #include "ash/shell.h" 9 #include "ash/shell.h"
10 #include "base/time/time.h" 10 #include "base/time/time.h"
11 #include "components/prefs/pref_registry_simple.h" 11 #include "components/prefs/pref_registry_simple.h"
12 #include "components/prefs/pref_service.h" 12 #include "components/prefs/pref_service.h"
13 #include "device/geolocation/geolocation_provider.h"
14 #include "device/geolocation/geoposition.h"
15 #include "third_party/icu/source/i18n/astro.h"
13 #include "ui/compositor/layer.h" 16 #include "ui/compositor/layer.h"
14 #include "ui/compositor/scoped_layer_animation_settings.h" 17 #include "ui/compositor/scoped_layer_animation_settings.h"
15 18
16 namespace ash { 19 namespace ash {
17 20
18 namespace { 21 namespace {
19 22
23 // Default start time at 6:00 PM as an offset from 00:00.
24 const int kDefaultStartTimeOffsetMinutes = 18 * 60;
25
26 // Default end time at 6:00 AM as an offset from 00:00.
27 const int kDefaultEndTimeOffsetMinutes = 6 * 60;
28
20 constexpr float kDefaultColorTemperature = 0.5f; 29 constexpr float kDefaultColorTemperature = 0.5f;
21 30
22 // The duration of the temperature change animation when the change is a result 31 // The duration of the temperature change animation for
23 // of a manual user setting. 32 // AnimationDurationType::kShort.
24 // TODO(afakhry): Add automatic schedule animation duration when you implement 33 constexpr int kManualAnimationDurationSec = 2;
25 // that part. It should be large enough (20 seconds as agreed) to give the user 34
26 // a nice smooth transition. 35 // The duration of the temperature change animation for
27 constexpr int kManualToggleAnimationDurationSec = 2; 36 // AnimationDurationType::kLong.
37 constexpr int kAutomaticAnimationDurationSec = 20;
38
39 class NightLightControllerDelegateImpl : public NightLightController::Delegate {
40 public:
41 NightLightControllerDelegateImpl() {
42 device::GeolocationProvider::GetInstance()
43 ->UserDidOptIntoLocationServices();
44 geolocation_subscription_ =
45 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
46 base::Bind(&NightLightControllerDelegateImpl::OnGeoPosition,
47 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
48 false /* enable_high_accuracy */);
49 }
50 ~NightLightControllerDelegateImpl() override = default;
51
52 // ash::NightLightController::Delegate:
53 base::Time GetNow() const override { return base::Time::Now(); }
54 base::Time GetSunsetTime() const override { return GetSunRiseSet(false); }
55 base::Time GetSunriseTime() const override { return GetSunRiseSet(true); }
56
57 private:
58 base::Time GetSunRiseSet(bool sunrise) const {
59 if (!position_.Validate()) {
60 return sunrise ? NightLightTime(kDefaultEndTimeOffsetMinutes).ToTime()
61 : NightLightTime(kDefaultStartTimeOffsetMinutes).ToTime();
62 }
63
64 icu::CalendarAstronomer astro(position_.longitude, position_.latitude);
65 // Note that the icu calendar return milliseconds since epoch, and
66 // base::Time::FromDoubleT() expects seconds.
67 return base::Time::FromDoubleT(astro.getSunRiseSet(sunrise) / 1000.0);
68 }
69
70 void OnGeoPosition(const device::Geoposition& pos) { position_ = pos; }
71
72 using LocationSubscription = device::GeolocationProvider::Subscription;
73 std::unique_ptr<LocationSubscription> geolocation_subscription_;
74
75 device::Geoposition position_;
76
77 DISALLOW_COPY_AND_ASSIGN(NightLightControllerDelegateImpl);
78 };
28 79
29 // Applies the given |layer_temperature| to all the layers of the root windows 80 // Applies the given |layer_temperature| to all the layers of the root windows
30 // with the given |animation_duration|. 81 // with the given |animation_duration|.
31 // |layer_temperature| is the ui::Layer floating-point value in the range of 82 // |layer_temperature| is the ui::Layer floating-point value in the range of
32 // 0.0f (least warm) to 1.0f (most warm). 83 // 0.0f (least warm) to 1.0f (most warm).
33 void ApplyColorTemperatureToLayers(float layer_temperature, 84 void ApplyColorTemperatureToLayers(float layer_temperature,
34 base::TimeDelta animation_duration) { 85 base::TimeDelta animation_duration) {
35 for (aura::Window* root_window : Shell::GetAllRootWindows()) { 86 for (aura::Window* root_window : Shell::GetAllRootWindows()) {
36 ui::Layer* layer = root_window->layer(); 87 ui::Layer* layer = root_window->layer();
37 88
38 ui::ScopedLayerAnimationSettings settings(layer->GetAnimator()); 89 ui::ScopedLayerAnimationSettings settings(layer->GetAnimator());
39 settings.SetTransitionDuration(animation_duration); 90 settings.SetTransitionDuration(animation_duration);
40 settings.SetPreemptionStrategy( 91 settings.SetPreemptionStrategy(
41 ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET); 92 ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET);
42 93
43 layer->SetLayerTemperature(layer_temperature); 94 layer->SetLayerTemperature(layer_temperature);
44 } 95 }
45 } 96 }
46 97
47 } // namespace 98 } // namespace
48 99
49 NightLightController::NightLightController( 100 NightLightController::NightLightController(
50 SessionController* session_controller) 101 SessionController* session_controller)
51 : session_controller_(session_controller) { 102 : session_controller_(session_controller),
103 delegate_(base::MakeUnique<NightLightControllerDelegateImpl>()) {
52 session_controller_->AddObserver(this); 104 session_controller_->AddObserver(this);
53 } 105 }
54 106
55 NightLightController::~NightLightController() { 107 NightLightController::~NightLightController() {
56 session_controller_->RemoveObserver(this); 108 session_controller_->RemoveObserver(this);
57 } 109 }
58 110
59 // static 111 // static
60 void NightLightController::RegisterPrefs(PrefRegistrySimple* registry) { 112 void NightLightController::RegisterPrefs(PrefRegistrySimple* registry) {
61 registry->RegisterBooleanPref(prefs::kNightLightEnabled, false); 113 registry->RegisterBooleanPref(prefs::kNightLightEnabled, false);
62 registry->RegisterDoublePref(prefs::kNightLightTemperature, 114 registry->RegisterDoublePref(prefs::kNightLightTemperature,
63 kDefaultColorTemperature); 115 kDefaultColorTemperature);
116 registry->RegisterIntegerPref(prefs::kNightLightScheduleType,
117 static_cast<int>(ScheduleType::kNever));
118 registry->RegisterIntegerPref(prefs::kNightLightCustomStartTime,
119 kDefaultStartTimeOffsetMinutes);
120 registry->RegisterIntegerPref(prefs::kNightLightCustomEndTime,
121 kDefaultEndTimeOffsetMinutes);
64 } 122 }
65 123
66 void NightLightController::AddObserver(Observer* observer) { 124 void NightLightController::AddObserver(Observer* observer) {
67 observers_.AddObserver(observer); 125 observers_.AddObserver(observer);
68 } 126 }
69 127
70 void NightLightController::RemoveObserver(Observer* observer) { 128 void NightLightController::RemoveObserver(Observer* observer) {
71 observers_.RemoveObserver(observer); 129 observers_.RemoveObserver(observer);
72 } 130 }
73 131
74 bool NightLightController::GetEnabled() const { 132 bool NightLightController::GetEnabled() const {
75 return active_user_pref_service_ && 133 return active_user_pref_service_ &&
76 active_user_pref_service_->GetBoolean(prefs::kNightLightEnabled); 134 active_user_pref_service_->GetBoolean(prefs::kNightLightEnabled);
77 } 135 }
78 136
79 float NightLightController::GetColorTemperature() const { 137 float NightLightController::GetColorTemperature() const {
80 if (active_user_pref_service_) 138 if (active_user_pref_service_)
81 return active_user_pref_service_->GetDouble(prefs::kNightLightTemperature); 139 return active_user_pref_service_->GetDouble(prefs::kNightLightTemperature);
82 140
83 return kDefaultColorTemperature; 141 return kDefaultColorTemperature;
84 } 142 }
85 143
86 void NightLightController::SetEnabled(bool enabled) { 144 NightLightController::ScheduleType NightLightController::GetScheduleType()
87 if (active_user_pref_service_) 145 const {
146 if (active_user_pref_service_) {
147 return static_cast<ScheduleType>(
148 active_user_pref_service_->GetInteger(prefs::kNightLightScheduleType));
149 }
150
151 return ScheduleType::kNever;
152 }
153
154 NightLightTime NightLightController::GetCustomStartTime() const {
155 if (active_user_pref_service_) {
156 return NightLightTime(active_user_pref_service_->GetInteger(
157 prefs::kNightLightCustomStartTime));
158 }
159
160 return NightLightTime(kDefaultStartTimeOffsetMinutes);
161 }
162
163 NightLightTime NightLightController::GetCustomEndTime() const {
164 if (active_user_pref_service_) {
165 return NightLightTime(
166 active_user_pref_service_->GetInteger(prefs::kNightLightCustomEndTime));
167 }
168
169 return NightLightTime(kDefaultEndTimeOffsetMinutes);
170 }
171
172 void NightLightController::SetEnabled(bool enabled,
173 AnimationDurationType animation_type) {
174 if (active_user_pref_service_) {
175 animation_type_ = animation_type;
88 active_user_pref_service_->SetBoolean(prefs::kNightLightEnabled, enabled); 176 active_user_pref_service_->SetBoolean(prefs::kNightLightEnabled, enabled);
177 }
89 } 178 }
90 179
91 void NightLightController::SetColorTemperature(float temperature) { 180 void NightLightController::SetColorTemperature(float temperature) {
92 DCHECK_GE(temperature, 0.0f); 181 DCHECK_GE(temperature, 0.0f);
93 DCHECK_LE(temperature, 1.0f); 182 DCHECK_LE(temperature, 1.0f);
94 if (active_user_pref_service_) { 183 if (active_user_pref_service_) {
95 active_user_pref_service_->SetDouble(prefs::kNightLightTemperature, 184 active_user_pref_service_->SetDouble(prefs::kNightLightTemperature,
96 temperature); 185 temperature);
97 } 186 }
98 } 187 }
99 188
189 void NightLightController::SetScheduleType(ScheduleType type) {
190 if (active_user_pref_service_) {
191 active_user_pref_service_->SetInteger(prefs::kNightLightScheduleType,
192 static_cast<int>(type));
193 }
194 }
195
196 void NightLightController::SetCustomStartTime(NightLightTime start_time) {
197 if (active_user_pref_service_) {
198 active_user_pref_service_->SetInteger(
199 prefs::kNightLightCustomStartTime,
200 start_time.offset_minutes_from_zero_hour());
201 }
202 }
203
204 void NightLightController::SetCustomEndTime(NightLightTime end_time) {
205 if (active_user_pref_service_) {
206 active_user_pref_service_->SetInteger(
207 prefs::kNightLightCustomEndTime,
208 end_time.offset_minutes_from_zero_hour());
209 }
210 }
211
100 void NightLightController::Toggle() { 212 void NightLightController::Toggle() {
101 SetEnabled(!GetEnabled()); 213 SetEnabled(!GetEnabled(), AnimationDurationType::kShort);
102 } 214 }
103 215
104 void NightLightController::OnActiveUserSessionChanged( 216 void NightLightController::OnActiveUserSessionChanged(
105 const AccountId& account_id) { 217 const AccountId& account_id) {
106 // Initial login and user switching in multi profiles. 218 // Initial login and user switching in multi profiles.
107 pref_change_registrar_.reset(); 219 pref_change_registrar_.reset();
108 active_user_pref_service_ = Shell::Get()->GetActiveUserPrefService(); 220 active_user_pref_service_ = Shell::Get()->GetActiveUserPrefService();
109 InitFromUserPrefs(); 221 InitFromUserPrefs();
110 } 222 }
111 223
112 void NightLightController::Refresh() { 224 void NightLightController::OverrideDelegateForTesting(
225 std::unique_ptr<Delegate> delegate) {
226 delegate_ = std::move(delegate);
227 }
228
229 void NightLightController::RefreshLayersTemperature() {
113 // TODO(afakhry): Add here refreshing of start and end times, when you 230 // TODO(afakhry): Add here refreshing of start and end times, when you
114 // implement the automatic schedule settings. 231 // implement the automatic schedule settings.
115 ApplyColorTemperatureToLayers( 232 ApplyColorTemperatureToLayers(
116 GetEnabled() ? GetColorTemperature() : 0.0f, 233 GetEnabled() ? GetColorTemperature() : 0.0f,
117 base::TimeDelta::FromSeconds(kManualToggleAnimationDurationSec)); 234 base::TimeDelta::FromSeconds(animation_type_ ==
235 AnimationDurationType::kShort
236 ? kManualAnimationDurationSec
237 : kAutomaticAnimationDurationSec));
238
239 // Reset the animation type back to manual to consume any automatically set
240 // animations.
241 consumed_animation_type_ = animation_type_;
242 animation_type_ = AnimationDurationType::kShort;
118 } 243 }
119 244
120 void NightLightController::StartWatchingPrefsChanges() { 245 void NightLightController::StartWatchingPrefsChanges() {
121 DCHECK(active_user_pref_service_); 246 DCHECK(active_user_pref_service_);
122 247
123 pref_change_registrar_ = base::MakeUnique<PrefChangeRegistrar>(); 248 pref_change_registrar_ = base::MakeUnique<PrefChangeRegistrar>();
124 pref_change_registrar_->Init(active_user_pref_service_); 249 pref_change_registrar_->Init(active_user_pref_service_);
125 pref_change_registrar_->Add( 250 pref_change_registrar_->Add(
126 prefs::kNightLightEnabled, 251 prefs::kNightLightEnabled,
127 base::Bind(&NightLightController::OnEnabledPrefChanged, 252 base::Bind(&NightLightController::OnEnabledPrefChanged,
128 base::Unretained(this))); 253 base::Unretained(this)));
129 pref_change_registrar_->Add( 254 pref_change_registrar_->Add(
130 prefs::kNightLightTemperature, 255 prefs::kNightLightTemperature,
131 base::Bind(&NightLightController::OnColorTemperaturePrefChanged, 256 base::Bind(&NightLightController::OnColorTemperaturePrefChanged,
132 base::Unretained(this))); 257 base::Unretained(this)));
258 pref_change_registrar_->Add(
259 prefs::kNightLightScheduleType,
260 base::Bind(&NightLightController::OnScheduleParamsPrefsChanged,
261 base::Unretained(this)));
262 pref_change_registrar_->Add(
263 prefs::kNightLightCustomStartTime,
264 base::Bind(&NightLightController::OnScheduleParamsPrefsChanged,
265 base::Unretained(this)));
266 pref_change_registrar_->Add(
267 prefs::kNightLightCustomEndTime,
268 base::Bind(&NightLightController::OnScheduleParamsPrefsChanged,
269 base::Unretained(this)));
133 } 270 }
134 271
135 void NightLightController::InitFromUserPrefs() { 272 void NightLightController::InitFromUserPrefs() {
136 if (!active_user_pref_service_) { 273 if (!active_user_pref_service_) {
137 // The pref_service can be null in ash_unittests. 274 // The pref_service can be null in ash_unittests.
138 return; 275 return;
139 } 276 }
140 277
141 StartWatchingPrefsChanges(); 278 StartWatchingPrefsChanges();
142 Refresh(); 279 Refresh(true /* did_schedule_change */);
143 NotifyStatusChanged(); 280 NotifyStatusChanged();
144 } 281 }
145 282
146 void NightLightController::NotifyStatusChanged() { 283 void NightLightController::NotifyStatusChanged() {
147 for (auto& observer : observers_) 284 for (auto& observer : observers_)
148 observer.OnNightLightEnabledChanged(GetEnabled()); 285 observer.OnNightLightEnabledChanged(GetEnabled());
149 } 286 }
150 287
151 void NightLightController::OnEnabledPrefChanged() { 288 void NightLightController::OnEnabledPrefChanged() {
152 DCHECK(active_user_pref_service_); 289 DCHECK(active_user_pref_service_);
153 Refresh(); 290 Refresh(false /* did_schedule_change */);
154 NotifyStatusChanged(); 291 NotifyStatusChanged();
155 } 292 }
156 293
157 void NightLightController::OnColorTemperaturePrefChanged() { 294 void NightLightController::OnColorTemperaturePrefChanged() {
158 DCHECK(active_user_pref_service_); 295 DCHECK(active_user_pref_service_);
159 Refresh(); 296 RefreshLayersTemperature();
297 }
298
299 void NightLightController::OnScheduleParamsPrefsChanged() {
300 DCHECK(active_user_pref_service_);
301 Refresh(true /* did_schedule_change */);
302 }
303
304 void NightLightController::Refresh(bool did_schedule_change) {
305 RefreshLayersTemperature();
306
307 const ScheduleType type = GetScheduleType();
308 switch (type) {
309 case ScheduleType::kNever:
310 timer_.Stop();
311 return;
312
313 case ScheduleType::kSunsetToSunrise:
314 RefreshScheduleTimer(delegate_->GetSunsetTime(),
315 delegate_->GetSunriseTime(), did_schedule_change);
316 return;
317
318 case ScheduleType::kCustom:
319 RefreshScheduleTimer(GetCustomStartTime().ToTime(),
320 GetCustomEndTime().ToTime(), did_schedule_change);
321 return;
322 }
323 }
324
325 void NightLightController::RefreshScheduleTimer(base::Time start_time,
326 base::Time end_time,
327 bool did_schedule_change) {
328 DCHECK_NE(ScheduleType::kNever, GetScheduleType());
329
330 // NOTE: Users can set any weird combinations.
331 if (end_time <= start_time) {
332 // Example:
333 // Start: 9:00 PM, End: 6:00 AM.
334 //
335 // 6:00 21:00
336 // <----- + ------------------ + ----->
337 // | |
338 // end start
339 //
340 // From our perspective, the end time is always a day later.
341 end_time += base::TimeDelta::FromDays(1);
342 }
343
344 DCHECK_GE(end_time, start_time);
345
346 // The target status that we need to set NightLight to now if a change of
347 // status is needed immediately.
348 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.
349
350 // Where are we now with respect to the start and end times?
351 const base::Time now = delegate_->GetNow();
352 if (now < start_time) {
353 // Example:
354 // 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.
355 //
356 // <----- + ----------- + ----------- + ----->
357 // | | |
358 // now start end
359 //
360 // In this case, we need to disable NightLight immediately if it's enabled.
361 immediate_target_status = false;
362 } else if (now >= start_time && now < end_time) {
363 // Example:
364 // Start: 6:00 PM today, End: 6:00 AM tomorrow, Now: 11:00PM.
365 //
366 // <----- + ----------- + ----------- + ----->
367 // | | |
368 // start now end
369 //
370 // Start NightLight right away. Our future start time is a day later than
371 // its current value.
372 immediate_target_status = true;
373 start_time += base::TimeDelta::FromDays(1);
374 } else { // now >= end_time.
375 // Example:
376 // Start: 6:00 PM today, End: 10:00 PM today, Now: 11:00PM.
377 //
378 // <----- + ----------- + ----------- + ----->
379 // | | |
380 // start end now
381 //
382 // In this case, our future start and end times are a day later from their
383 // current values. NightLight needs to be ended immediately if it's already
384 // enabled.
385 immediate_target_status = false;
386 start_time += base::TimeDelta::FromDays(1);
387 end_time += base::TimeDelta::FromDays(1);
388 }
389
390 // After the above processing, the start and end time are all in the future.
391 DCHECK_GE(start_time, now);
392 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.
393
394 if (did_schedule_change && immediate_target_status != GetEnabled()) {
395 // If the change in the schedule introduces a change in the status, then
396 // calling SetEnabled() is all we need, since it will trigger a change in
397 // the user prefs to which we will respond by calling Refresh(). This will
398 // end up in this function again, adjusting all the needed schedules.
399 SetEnabled(immediate_target_status, AnimationDurationType::kShort);
400 return;
401 }
402
403 // We reach here in one of the following conditions:
404 // 1) If schedule changes don't result in changes in the status, we need to
405 // explicitly update the timer to re-schedule the next toggle to account for
406 // any changes.
407 // 2) The user has just manually toggled the status of NightLight either from
408 // the System Menu or System Settings. In this case, we respect the user
409 // wish and maintain the current status that he desires, but we schedule the
410 // status to be toggled according to the time that corresponds with the
411 // opposite status of the current one.
412 ScheduleNextToggle(GetEnabled() ? end_time - now : start_time - now);
413 }
414
415 void NightLightController::ScheduleNextToggle(base::TimeDelta delay) {
416 timer_.Start(
417 FROM_HERE, delay,
418 base::Bind(&NightLightController::SetEnabled, base::Unretained(this),
419 GetEnabled() ? false : true, AnimationDurationType::kLong));
James Cook 2017/05/23 16:34:58 nit: !GetEnabled()
afakhry 2017/05/24 04:21:10 Done.
160 } 420 }
161 421
162 } // namespace ash 422 } // namespace ash
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698