scheduled_feature.cc 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423
  1. // Copyright 2021 The Chromium Authors. All rights reserved.
  2. // Use of this source code is governed by a BSD-style license that can be
  3. // found in the LICENSE file.
  4. #include "ash/system/scheduled_feature/scheduled_feature.h"
  5. #include <cmath>
  6. #include <memory>
  7. #include "ash/constants/ash_features.h"
  8. #include "ash/constants/ash_pref_names.h"
  9. #include "ash/public/cpp/notification_utils.h"
  10. #include "ash/session/session_controller_impl.h"
  11. #include "ash/shell.h"
  12. #include "ash/strings/grit/ash_strings.h"
  13. #include "ash/system/geolocation/geolocation_controller.h"
  14. #include "ash/system/model/system_tray_model.h"
  15. #include "base/bind.h"
  16. #include "base/cxx17_backports.h"
  17. #include "base/i18n/time_formatting.h"
  18. #include "base/logging.h"
  19. #include "base/notreached.h"
  20. #include "base/time/time.h"
  21. #include "components/prefs/pref_registry_simple.h"
  22. #include "components/prefs/pref_service.h"
  23. #include "third_party/icu/source/i18n/astro.h"
  24. #include "ui/aura/env.h"
  25. #include "ui/base/l10n/l10n_util.h"
  26. #include "ui/gfx/geometry/vector3d_f.h"
  27. namespace ash {
  28. namespace {
  29. // Default start time at 6:00 PM as an offset from 00:00.
  30. constexpr int kDefaultStartTimeOffsetMinutes = 18 * 60;
  31. // Default end time at 6:00 AM as an offset from 00:00.
  32. constexpr int kDefaultEndTimeOffsetMinutes = 6 * 60;
  33. } // namespace
  34. ScheduledFeature::ScheduledFeature(
  35. const std::string prefs_path_enabled,
  36. const std::string prefs_path_schedule_type,
  37. const std::string prefs_path_custom_start_time,
  38. const std::string prefs_path_custom_end_time)
  39. : prefs_path_enabled_(prefs_path_enabled),
  40. prefs_path_schedule_type_(prefs_path_schedule_type),
  41. prefs_path_custom_start_time_(prefs_path_custom_start_time),
  42. prefs_path_custom_end_time_(prefs_path_custom_end_time),
  43. geolocation_controller_(ash::GeolocationController::Get()) {
  44. Shell::Get()->session_controller()->AddObserver(this);
  45. aura::Env::GetInstance()->AddObserver(this);
  46. chromeos::PowerManagerClient::Get()->AddObserver(this);
  47. // Check that both start or end times are supplied or both are absent.
  48. DCHECK_EQ(prefs_path_custom_start_time_.empty(),
  49. prefs_path_custom_end_time_.empty());
  50. }
  51. ScheduledFeature::~ScheduledFeature() {
  52. chromeos::PowerManagerClient::Get()->RemoveObserver(this);
  53. aura::Env::GetInstance()->RemoveObserver(this);
  54. Shell::Get()->session_controller()->RemoveObserver(this);
  55. }
  56. bool ScheduledFeature::GetEnabled() const {
  57. return active_user_pref_service_ &&
  58. active_user_pref_service_->GetBoolean(prefs_path_enabled_);
  59. }
  60. ScheduleType ScheduledFeature::GetScheduleType() const {
  61. if (active_user_pref_service_) {
  62. return static_cast<ScheduleType>(
  63. active_user_pref_service_->GetInteger(prefs_path_schedule_type_));
  64. }
  65. return ScheduleType::kNone;
  66. }
  67. TimeOfDay ScheduledFeature::GetCustomStartTime() const {
  68. DCHECK(!prefs_path_custom_start_time_.empty());
  69. return TimeOfDay(active_user_pref_service_
  70. ? active_user_pref_service_->GetInteger(
  71. prefs_path_custom_start_time_)
  72. : kDefaultStartTimeOffsetMinutes)
  73. .SetClock(clock_);
  74. }
  75. TimeOfDay ScheduledFeature::GetCustomEndTime() const {
  76. DCHECK(!prefs_path_custom_end_time_.empty());
  77. return TimeOfDay(active_user_pref_service_
  78. ? active_user_pref_service_->GetInteger(
  79. prefs_path_custom_end_time_)
  80. : kDefaultEndTimeOffsetMinutes)
  81. .SetClock(clock_);
  82. }
  83. bool ScheduledFeature::IsNowWithinSunsetSunrise() const {
  84. // The times below are all on the same calendar day.
  85. const base::Time now = GetNow();
  86. return now < geolocation_controller_->GetSunriseTime() ||
  87. now > geolocation_controller_->GetSunsetTime();
  88. }
  89. void ScheduledFeature::SetEnabled(bool enabled) {
  90. if (active_user_pref_service_)
  91. active_user_pref_service_->SetBoolean(prefs_path_enabled_, enabled);
  92. }
  93. void ScheduledFeature::SetScheduleType(ScheduleType type) {
  94. if (!active_user_pref_service_)
  95. return;
  96. if (type == ScheduleType::kCustom && (prefs_path_custom_start_time_.empty() ||
  97. prefs_path_custom_end_time_.empty())) {
  98. NOTREACHED();
  99. return;
  100. }
  101. active_user_pref_service_->SetInteger(prefs_path_schedule_type_,
  102. static_cast<int>(type));
  103. }
  104. void ScheduledFeature::SetCustomStartTime(TimeOfDay start_time) {
  105. DCHECK(!prefs_path_custom_start_time_.empty());
  106. if (active_user_pref_service_) {
  107. active_user_pref_service_->SetInteger(
  108. prefs_path_custom_start_time_,
  109. start_time.offset_minutes_from_zero_hour());
  110. }
  111. }
  112. void ScheduledFeature::SetCustomEndTime(TimeOfDay end_time) {
  113. DCHECK(!prefs_path_custom_end_time_.empty());
  114. if (active_user_pref_service_) {
  115. active_user_pref_service_->SetInteger(
  116. prefs_path_custom_end_time_, end_time.offset_minutes_from_zero_hour());
  117. }
  118. }
  119. void ScheduledFeature::OnActiveUserPrefServiceChanged(
  120. PrefService* pref_service) {
  121. if (pref_service == active_user_pref_service_)
  122. return;
  123. // Initial login and user switching in multi profiles.
  124. active_user_pref_service_ = pref_service;
  125. InitFromUserPrefs();
  126. }
  127. void ScheduledFeature::OnGeopositionChanged(bool possible_change_in_timezone) {
  128. DCHECK(GetScheduleType() != ScheduleType::kNone);
  129. VLOG(1) << "Received new geoposition.";
  130. // We only keep manual toggles if there's no change in timezone.
  131. const bool keep_manual_toggles_during_schedules =
  132. !possible_change_in_timezone;
  133. Refresh(/*did_schedule_change=*/true, keep_manual_toggles_during_schedules);
  134. }
  135. void ScheduledFeature::SuspendDone(base::TimeDelta sleep_duration) {
  136. // Time changes while the device is suspended. We need to refresh the schedule
  137. // upon device resume to know what the status should be now.
  138. Refresh(/*did_schedule_change=*/true,
  139. /*keep_manual_toggles_during_schedules=*/true);
  140. }
  141. void ScheduledFeature::SetClockForTesting(base::Clock* clock) {
  142. clock_ = clock;
  143. }
  144. base::Time ScheduledFeature::GetNow() const {
  145. return clock_ ? clock_->Now() : base::Time::Now();
  146. }
  147. bool ScheduledFeature::MaybeRestoreSchedule() {
  148. DCHECK(active_user_pref_service_);
  149. DCHECK_NE(GetScheduleType(), ScheduleType::kNone);
  150. auto iter = per_user_schedule_target_state_.find(active_user_pref_service_);
  151. if (iter == per_user_schedule_target_state_.end())
  152. return false;
  153. ScheduleTargetState& target_state = iter->second;
  154. const base::Time now = GetNow();
  155. // It may be that the device was suspended for a very long time that the
  156. // target time is no longer valid.
  157. if (target_state.target_time <= now)
  158. return false;
  159. VLOG(1) << "Restoring a previous schedule.";
  160. DCHECK_NE(GetEnabled(), target_state.target_status);
  161. ScheduleNextToggle(target_state.target_time - now);
  162. return true;
  163. }
  164. void ScheduledFeature::StartWatchingPrefsChanges() {
  165. DCHECK(active_user_pref_service_);
  166. pref_change_registrar_ = std::make_unique<PrefChangeRegistrar>();
  167. pref_change_registrar_->Init(active_user_pref_service_);
  168. pref_change_registrar_->Add(
  169. prefs_path_enabled_,
  170. base::BindRepeating(&ScheduledFeature::OnEnabledPrefChanged,
  171. base::Unretained(this)));
  172. pref_change_registrar_->Add(
  173. prefs_path_schedule_type_,
  174. base::BindRepeating(&ScheduledFeature::OnScheduleTypePrefChanged,
  175. base::Unretained(this),
  176. /*keep_manual_toggles_during_schedules=*/false));
  177. if (!prefs_path_custom_start_time_.empty()) {
  178. pref_change_registrar_->Add(
  179. prefs_path_custom_start_time_,
  180. base::BindRepeating(&ScheduledFeature::OnCustomSchedulePrefsChanged,
  181. base::Unretained(this)));
  182. }
  183. if (!prefs_path_custom_end_time_.empty()) {
  184. pref_change_registrar_->Add(
  185. prefs_path_custom_end_time_,
  186. base::BindRepeating(&ScheduledFeature::OnCustomSchedulePrefsChanged,
  187. base::Unretained(this)));
  188. }
  189. }
  190. void ScheduledFeature::InitFromUserPrefs() {
  191. StartWatchingPrefsChanges();
  192. OnScheduleTypePrefChanged(/*keep_manual_toggles_during_schedules=*/true);
  193. is_first_user_init_ = false;
  194. }
  195. void ScheduledFeature::OnEnabledPrefChanged() {
  196. const bool enabled = GetEnabled();
  197. VLOG(1) << "Enable state changed. New state: " << enabled << ".";
  198. DCHECK(active_user_pref_service_);
  199. Refresh(/*did_schedule_change=*/false,
  200. /*keep_manual_toggles_during_schedules=*/false);
  201. }
  202. void ScheduledFeature::OnScheduleTypePrefChanged(
  203. bool keep_manual_toggles_during_schedules) {
  204. const ScheduleType schedule_type = GetScheduleType();
  205. // To prevent adding (or removing) an observer twice in a row when switching
  206. // between different users, we need to check `is_observing_geolocation_`.
  207. if (schedule_type == ScheduleType::kNone && is_observing_geolocation_) {
  208. geolocation_controller_->RemoveObserver(this);
  209. is_observing_geolocation_ = false;
  210. } else if (schedule_type != ScheduleType::kNone &&
  211. !is_observing_geolocation_) {
  212. geolocation_controller_->AddObserver(this);
  213. is_observing_geolocation_ = true;
  214. }
  215. Refresh(/*did_schedule_change=*/true, keep_manual_toggles_during_schedules);
  216. }
  217. void ScheduledFeature::OnCustomSchedulePrefsChanged() {
  218. DCHECK(active_user_pref_service_);
  219. Refresh(/*did_schedule_change=*/true,
  220. /*keep_manual_toggles_during_schedules=*/false);
  221. }
  222. void ScheduledFeature::Refresh(bool did_schedule_change,
  223. bool keep_manual_toggles_during_schedules) {
  224. switch (GetScheduleType()) {
  225. case ScheduleType::kNone:
  226. timer_.Stop();
  227. RefreshFeatureState();
  228. return;
  229. case ScheduleType::kSunsetToSunrise:
  230. RefreshScheduleTimer(geolocation_controller_->GetSunsetTime(),
  231. geolocation_controller_->GetSunriseTime(),
  232. did_schedule_change,
  233. keep_manual_toggles_during_schedules);
  234. return;
  235. case ScheduleType::kCustom:
  236. RefreshScheduleTimer(
  237. GetCustomStartTime().ToTimeToday(), GetCustomEndTime().ToTimeToday(),
  238. did_schedule_change, keep_manual_toggles_during_schedules);
  239. return;
  240. }
  241. }
  242. void ScheduledFeature::RefreshScheduleTimer(
  243. base::Time start_time,
  244. base::Time end_time,
  245. bool did_schedule_change,
  246. bool keep_manual_toggles_during_schedules) {
  247. DCHECK(GetScheduleType() != ScheduleType::kNone);
  248. if (keep_manual_toggles_during_schedules && MaybeRestoreSchedule()) {
  249. RefreshFeatureState();
  250. return;
  251. }
  252. // NOTE: Users can set any weird combinations.
  253. const base::Time now = GetNow();
  254. if (end_time <= start_time) {
  255. // Example:
  256. // Start: 9:00 PM, End: 6:00 AM.
  257. //
  258. // 6:00 21:00
  259. // <----- + ------------------ + ----->
  260. // | |
  261. // end start
  262. //
  263. // Note that the above times are times of day (today). It is important to
  264. // know where "now" is with respect to these times to decide how to adjust
  265. // them.
  266. if (end_time >= now) {
  267. // If the end time (today) is greater than the time now, this means "now"
  268. // is within the feature schedule, and the start time is actually
  269. // yesterday. The above timeline is interpreted as:
  270. //
  271. // 21:00 (-1day) 6:00
  272. // <----- + ----------- + ------ + ----->
  273. // | | |
  274. // start now end
  275. //
  276. start_time -= base::Days(1);
  277. } else {
  278. // Two possibilities here:
  279. // - Either "now" is greater than the end time, but less than start time.
  280. // This means the feature is outside the schedule, waiting for the next
  281. // start time. The end time is actually a day later.
  282. // - Or "now" is greater than both the start and end times. This means
  283. // the feature is within the schedule, waiting to turn off at the next
  284. // end time, which is also a day later.
  285. end_time += base::Days(1);
  286. }
  287. }
  288. DCHECK_GE(end_time, start_time);
  289. // The target status that we need to set the feature to now if a change of
  290. // status is needed immediately.
  291. bool enable_now = false;
  292. // Where are we now with respect to the start and end times?
  293. if (now < start_time) {
  294. // Example:
  295. // Start: 6:00 PM today, End: 6:00 AM tomorrow, Now: 4:00 PM.
  296. //
  297. // <----- + ----------- + ----------- + ----->
  298. // | | |
  299. // now start end
  300. //
  301. // In this case, we need to disable the feature immediately if it's enabled.
  302. enable_now = false;
  303. } else if (now >= start_time && now < end_time) {
  304. // Example:
  305. // Start: 6:00 PM today, End: 6:00 AM tomorrow, Now: 11:00 PM.
  306. //
  307. // <----- + ----------- + ----------- + ----->
  308. // | | |
  309. // start now end
  310. //
  311. // Turn the feature on right away. Our future start time is a day later than
  312. // its current value.
  313. enable_now = true;
  314. start_time += base::Days(1);
  315. } else { // now >= end_time.
  316. // Example:
  317. // Start: 6:00 PM today, End: 10:00 PM today, Now: 11:00 PM.
  318. //
  319. // <----- + ----------- + ----------- + ----->
  320. // | | |
  321. // start end now
  322. //
  323. // In this case, our future start and end times are a day later from their
  324. // current values. The feature needs to be off immediately if it's already
  325. // enabled.
  326. enable_now = false;
  327. start_time += base::Days(1);
  328. end_time += base::Days(1);
  329. }
  330. // After the above processing, the start and end time are all in the future.
  331. DCHECK_GE(start_time, now);
  332. DCHECK_GE(end_time, now);
  333. if (did_schedule_change && enable_now != GetEnabled()) {
  334. // If the change in the schedule introduces a change in the status, then
  335. // calling SetEnabled() is all we need, since it will trigger a change in
  336. // the user prefs to which we will respond by calling Refresh(). This will
  337. // end up in this function again, adjusting all the needed schedules.
  338. SetEnabled(enable_now);
  339. return;
  340. }
  341. // We reach here in one of the following conditions:
  342. // 1) If schedule changes don't result in changes in the status, we need to
  343. // explicitly update the timer to re-schedule the next toggle to account for
  344. // any changes.
  345. // 2) The user has just manually toggled the status of the feature either from
  346. // the System Menu or System Settings. In this case, we respect the user
  347. // wish and maintain the current status that they desire, but we schedule the
  348. // status to be toggled according to the time that corresponds with the
  349. // opposite status of the current one.
  350. ScheduleNextToggle(GetEnabled() ? end_time - now : start_time - now);
  351. RefreshFeatureState();
  352. }
  353. void ScheduledFeature::ScheduleNextToggle(base::TimeDelta delay) {
  354. DCHECK(active_user_pref_service_);
  355. const bool new_status = !GetEnabled();
  356. const base::Time target_time = GetNow() + delay;
  357. per_user_schedule_target_state_[active_user_pref_service_] =
  358. ScheduleTargetState{target_time, new_status};
  359. VLOG(1) << "Setting " << GetFeatureName() << " to toggle to "
  360. << (new_status ? "enabled" : "disabled") << " at "
  361. << base::TimeFormatTimeOfDay(target_time);
  362. timer_.Start(FROM_HERE, delay,
  363. base::BindOnce(&ScheduledFeature::SetEnabled,
  364. base::Unretained(this), new_status));
  365. }
  366. } // namespace ash