1 // Copyright 2019 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
5 #include "chrome/browser/chromeos/child_accounts/time_limits/app_time_controller.h"
6
7 #include "ash/public/cpp/notification_utils.h"
8 #include "base/bind.h"
9 #include "base/feature_list.h"
10 #include "base/logging.h"
11 #include "base/metrics/histogram_functions.h"
12 #include "base/strings/strcat.h"
13 #include "base/strings/string16.h"
14 #include "base/strings/string_util.h"
15 #include "base/strings/utf_string_conversions.h"
16 #include "base/values.h"
17 #include "chrome/browser/chromeos/child_accounts/child_user_service.h"
18 #include "chrome/browser/chromeos/child_accounts/time_limits/app_activity_registry.h"
19 #include "chrome/browser/chromeos/child_accounts/time_limits/app_service_wrapper.h"
20 #include "chrome/browser/chromeos/child_accounts/time_limits/app_time_limit_utils.h"
21 #include "chrome/browser/chromeos/child_accounts/time_limits/app_time_limits_allowlist_policy_wrapper.h"
22 #include "chrome/browser/chromeos/child_accounts/time_limits/app_time_policy_helpers.h"
23 #include "chrome/browser/chromeos/child_accounts/time_limits/app_types.h"
24 #include "chrome/browser/chromeos/child_accounts/time_limits/web_time_activity_provider.h"
25 #include "chrome/browser/chromeos/child_accounts/time_limits/web_time_limit_enforcer.h"
26 #include "chrome/browser/extensions/launch_util.h"
27 #include "chrome/browser/notifications/notification_display_service.h"
28 #include "chrome/browser/notifications/notification_handler.h"
29 #include "chrome/browser/profiles/profile.h"
30 #include "chrome/browser/ui/app_list/arc/arc_app_utils.h"
31 #include "chrome/common/chrome_features.h"
32 #include "chrome/common/pref_names.h"
33 #include "chrome/grit/generated_resources.h"
34 #include "chromeos/ui/vector_icons/vector_icons.h"
35 #include "components/prefs/pref_change_registrar.h"
36 #include "components/prefs/pref_registry_simple.h"
37 #include "components/prefs/pref_service.h"
38 #include "components/services/app_service/public/mojom/types.mojom.h"
39 #include "extensions/browser/extension_prefs.h"
40 #include "extensions/browser/extension_registry.h"
41 #include "extensions/common/extension.h"
42 #include "ui/base/l10n/l10n_util.h"
43 #include "ui/base/l10n/time_format.h"
44 #include "ui/gfx/image/image.h"
45 #include "ui/gfx/image/image_skia.h"
46 #include "ui/gfx/vector_icon_types.h"
47 #include "ui/message_center/public/cpp/notification.h"
48 #include "ui/message_center/public/cpp/notification_delegate.h"
49 #include "ui/message_center/public/cpp/notifier_id.h"
50
51 namespace chromeos {
52 namespace app_time {
53
54 const char kAppsWithTimeLimitMetric[] =
55 "SupervisedUsers.PerAppTimeLimits.AppsWithTimeLimit";
56 const char kBlockedAppsCountMetric[] =
57 "SupervisedUsers.PerAppTimeLimits.BlockedAppsCount";
58 const char kPolicyChangeCountMetric[] =
59 "SupervisedUsers.PerAppTimeLimits.PolicyChangeCount";
60 const char kEngagementMetric[] = "SupervisedUsers.PerAppTimeLimits.Engagement";
61
62 namespace {
63
64 constexpr base::TimeDelta kDay = base::TimeDelta::FromHours(24);
65
66 // Family link notifier id.
67 constexpr char kFamilyLinkSourceId[] = "family-link";
68
69 // Time limit reaching id. This id will be appended by the application name. The
70 // 5 minute and one minute notifications for the same app will have the same id.
71 constexpr char kAppTimeLimitReachingNotificationId[] =
72 "time-limit-reaching-id-";
73
74 // Time limit updated id. This id will be appended by the application name.
75 constexpr char kAppTimeLimitUpdateNotificationId[] = "time-limit-updated-id-";
76
77 // Used to convert |time_limit| to string. |cutoff| specifies whether the
78 // formatted result will be one value or two values: for example if the time
79 // delta is 2 hours 30 minutes: |cutoff| of <2 will result in "2 hours" and
80 // |cutoff| of 3 will result in "2 hours and 30 minutes".
GetTimeLimitMessage(base::TimeDelta time_limit,int cutoff)81 base::string16 GetTimeLimitMessage(base::TimeDelta time_limit, int cutoff) {
82 return ui::TimeFormat::Detailed(ui::TimeFormat::Format::FORMAT_DURATION,
83 ui::TimeFormat::Length::LENGTH_LONG, cutoff,
84 time_limit);
85 }
86
GetNotificationTitleFor(const base::string16 & app_name,AppNotification notification)87 base::string16 GetNotificationTitleFor(const base::string16& app_name,
88 AppNotification notification) {
89 switch (notification) {
90 case AppNotification::kFiveMinutes:
91 case AppNotification::kOneMinute:
92 return l10n_util::GetStringFUTF16(
93 IDS_APP_TIME_LIMIT_APP_WILL_PAUSE_SYSTEM_NOTIFICATION_TITLE,
94 app_name);
95 case AppNotification::kBlocked:
96 case AppNotification::kAvailable:
97 case AppNotification::kTimeLimitChanged:
98 return l10n_util::GetStringUTF16(
99 IDS_APP_TIME_LIMIT_APP_TIME_LIMIT_SET_SYSTEM_NOTIFICATION_TITLE);
100 default:
101 NOTREACHED();
102 return base::EmptyString16();
103 }
104 }
105
GetNotificationMessageFor(const base::string16 & app_name,AppNotification notification,base::Optional<base::TimeDelta> time_limit)106 base::string16 GetNotificationMessageFor(
107 const base::string16& app_name,
108 AppNotification notification,
109 base::Optional<base::TimeDelta> time_limit) {
110 switch (notification) {
111 case AppNotification::kFiveMinutes:
112 return l10n_util::GetStringFUTF16(
113 IDS_APP_TIME_LIMIT_APP_WILL_PAUSE_SYSTEM_NOTIFICATION_MESSAGE,
114 GetTimeLimitMessage(base::TimeDelta::FromMinutes(5), /* cutoff */ 1));
115 case AppNotification::kOneMinute:
116 return l10n_util::GetStringFUTF16(
117 IDS_APP_TIME_LIMIT_APP_WILL_PAUSE_SYSTEM_NOTIFICATION_MESSAGE,
118 GetTimeLimitMessage(base::TimeDelta::FromMinutes(1), /* cutoff */ 1));
119 case AppNotification::kTimeLimitChanged:
120 return time_limit
121 ? l10n_util::GetStringFUTF16(
122 IDS_APP_TIME_LIMIT_APP_TIME_LIMIT_SET_SYSTEM_NOTIFICATION_MESSAGE,
123 GetTimeLimitMessage(*time_limit, /* cutoff */ 3),
124 app_name)
125 : l10n_util::GetStringFUTF16(
126 IDS_APP_TIME_LIMIT_APP_TIME_LIMIT_REMOVED_SYSTEM_NOTIFICATION_MESSAGE,
127 app_name);
128 case AppNotification::kBlocked:
129 return l10n_util::GetStringFUTF16(
130 IDS_APP_TIME_LIMIT_APP_BLOCKED_NOTIFICATION_MESSAGE, app_name);
131
132 case AppNotification::kAvailable:
133 return l10n_util::GetStringFUTF16(
134 IDS_APP_TIME_LIMIT_APP_AVAILABLE_NOTIFICATION_MESSAGE, app_name);
135 default:
136 NOTREACHED();
137 return base::EmptyString16();
138 }
139 }
140
GetNotificationIdFor(const std::string & app_name,AppNotification notification)141 std::string GetNotificationIdFor(const std::string& app_name,
142 AppNotification notification) {
143 std::string notification_id;
144 switch (notification) {
145 case AppNotification::kFiveMinutes:
146 case AppNotification::kOneMinute:
147 notification_id = kAppTimeLimitReachingNotificationId;
148 break;
149 case AppNotification::kTimeLimitChanged:
150 case AppNotification::kBlocked:
151 case AppNotification::kAvailable:
152 notification_id = kAppTimeLimitUpdateNotificationId;
153 break;
154 default:
155 NOTREACHED();
156 notification_id = "";
157 break;
158 }
159 return base::StrCat({notification_id, app_name});
160 }
161
IsAppOpenedInChrome(const AppId & app_id,Profile * profile)162 bool IsAppOpenedInChrome(const AppId& app_id, Profile* profile) {
163 if (app_id.app_type() != apps::mojom::AppType::kExtension &&
164 app_id.app_type() != apps::mojom::AppType::kWeb) {
165 return false;
166 }
167
168 // It is a web or extension.
169 const extensions::Extension* extension =
170 extensions::ExtensionRegistry::Get(profile)->GetInstalledExtension(
171 app_id.app_id());
172 if (!extension)
173 return false;
174
175 extensions::LaunchContainer launch_container = extensions::GetLaunchContainer(
176 extensions::ExtensionPrefs::Get(profile), extension);
177 return launch_container == extensions::LaunchContainer::kLaunchContainerTab;
178 }
179
180 } // namespace
181
TestApi(AppTimeController * controller)182 AppTimeController::TestApi::TestApi(AppTimeController* controller)
183 : controller_(controller) {}
184
185 AppTimeController::TestApi::~TestApi() = default;
186
SetLastResetTime(base::Time time)187 void AppTimeController::TestApi::SetLastResetTime(base::Time time) {
188 controller_->SetLastResetTime(time);
189 }
190
GetNextResetTime() const191 base::Time AppTimeController::TestApi::GetNextResetTime() const {
192 return controller_->GetNextResetTime();
193 }
194
GetLastResetTime() const195 base::Time AppTimeController::TestApi::GetLastResetTime() const {
196 return controller_->last_limits_reset_time_;
197 }
198
app_registry()199 AppActivityRegistry* AppTimeController::TestApi::app_registry() {
200 return controller_->app_registry_.get();
201 }
202
203 // static
ArePerAppTimeLimitsEnabled()204 bool AppTimeController::ArePerAppTimeLimitsEnabled() {
205 return base::FeatureList::IsEnabled(features::kPerAppTimeLimits);
206 }
207
IsAppActivityReportingEnabled()208 bool AppTimeController::IsAppActivityReportingEnabled() {
209 return base::FeatureList::IsEnabled(features::kAppActivityReporting);
210 }
211
212 // static
RegisterProfilePrefs(PrefRegistrySimple * registry)213 void AppTimeController::RegisterProfilePrefs(PrefRegistrySimple* registry) {
214 registry->RegisterInt64Pref(prefs::kPerAppTimeLimitsLastResetTime, 0);
215 registry->RegisterDictionaryPref(prefs::kPerAppTimeLimitsPolicy);
216 registry->RegisterDictionaryPref(prefs::kPerAppTimeLimitsAllowlistPolicy);
217 }
218
AppTimeController(Profile * profile)219 AppTimeController::AppTimeController(Profile* profile)
220 : profile_(profile),
221 app_service_wrapper_(std::make_unique<AppServiceWrapper>(profile)),
222 app_registry_(
223 std::make_unique<AppActivityRegistry>(app_service_wrapper_.get(),
224 this,
225 profile->GetPrefs())),
226 web_time_activity_provider_(std::make_unique<WebTimeActivityProvider>(
227 this,
228 app_service_wrapper_.get())) {
229 DCHECK(profile);
230
231 if (WebTimeLimitEnforcer::IsEnabled())
232 web_time_enforcer_ = std::make_unique<WebTimeLimitEnforcer>(this);
233
234 PrefService* pref_service = profile->GetPrefs();
235 RegisterProfilePrefObservers(pref_service);
236 TimeLimitsAllowlistPolicyUpdated(prefs::kPerAppTimeLimitsAllowlistPolicy);
237 TimeLimitsPolicyUpdated(prefs::kPerAppTimeLimitsPolicy);
238
239 // Restore the last reset time. If reset time has have been crossed, triggers
240 // AppActivityRegistry to clear up the running active times of applications.
241 RestoreLastResetTime();
242
243 // Start observing system clock client and time zone settings.
244 auto* system_clock_client = SystemClockClient::Get();
245 // SystemClockClient may not be initialized in some tests.
246 if (system_clock_client)
247 system_clock_client->AddObserver(this);
248
249 auto* time_zone_settings = system::TimezoneSettings::GetInstance();
250 if (time_zone_settings)
251 time_zone_settings->AddObserver(this);
252
253 // Start observing |app_registry_|
254 app_registry_->AddAppStateObserver(this);
255
256 // AppActivityRegistry may have missed |OnAppInstalled| calls. Notify it.
257 app_registry_->SetInstalledApps(app_service_wrapper_->GetInstalledApps());
258
259 // Record enagement metrics.
260 base::UmaHistogramCounts1000(kEngagementMetric, apps_with_limit_);
261
262 // If chrome is paused at the beginning of the session, notify
263 // web_time_enforcer directly. This is a workaround for bug in AppService that
264 // occurs at the beginning of the session. It could be removed when
265 // AppService successfully calls OnWebTimeLimitReached at the beginning of
266 // sessions.
267 if (app_registry_->IsAppInstalled(GetChromeAppId()) &&
268 app_registry_->IsAppTimeLimitReached(GetChromeAppId())) {
269 base::Optional<AppLimit> web_time_limit = app_registry_->GetWebTimeLimit();
270 DCHECK(web_time_limit);
271 DCHECK(web_time_limit->daily_limit());
272 DCHECK(web_time_enforcer_);
273 web_time_enforcer_->OnWebTimeLimitReached(
274 web_time_limit->daily_limit().value());
275 }
276 }
277
~AppTimeController()278 AppTimeController::~AppTimeController() {
279 app_registry_->RemoveAppStateObserver(this);
280
281 auto* time_zone_settings = system::TimezoneSettings::GetInstance();
282 if (time_zone_settings)
283 time_zone_settings->RemoveObserver(this);
284
285 auto* system_clock_client = SystemClockClient::Get();
286 if (system_clock_client)
287 system_clock_client->RemoveObserver(this);
288 }
289
IsExtensionAllowlisted(const std::string & extension_id) const290 bool AppTimeController::IsExtensionAllowlisted(
291 const std::string& extension_id) const {
292 return true;
293 }
294
GetTimeLimitForApp(const std::string & app_service_id,apps::mojom::AppType app_type) const295 base::Optional<base::TimeDelta> AppTimeController::GetTimeLimitForApp(
296 const std::string& app_service_id,
297 apps::mojom::AppType app_type) const {
298 const app_time::AppId app_id =
299 app_service_wrapper_->AppIdFromAppServiceId(app_service_id, app_type);
300 return app_registry_->GetTimeLimit(app_id);
301 }
302
RecordMetricsOnShutdown() const303 void AppTimeController::RecordMetricsOnShutdown() const {
304 base::UmaHistogramCounts1000(kPolicyChangeCountMetric,
305 patl_policy_update_count_);
306 }
307
SystemClockUpdated()308 void AppTimeController::SystemClockUpdated() {
309 if (HasTimeCrossedResetBoundary())
310 OnResetTimeReached();
311 }
312
TimezoneChanged(const icu::TimeZone & timezone)313 void AppTimeController::TimezoneChanged(const icu::TimeZone& timezone) {
314 // Timezone changes may not require us to reset information,
315 // however, they may require updating the scheduled reset time.
316 ScheduleForTimeLimitReset();
317 }
318
RegisterProfilePrefObservers(PrefService * pref_service)319 void AppTimeController::RegisterProfilePrefObservers(
320 PrefService* pref_service) {
321 pref_registrar_ = std::make_unique<PrefChangeRegistrar>();
322 pref_registrar_->Init(pref_service);
323
324 // Adds callbacks to observe policy pref changes.
325 // Using base::Unretained(this) is safe here because when |pref_registrar_|
326 // gets destroyed, it will remove the observers from PrefService.
327 pref_registrar_->Add(
328 prefs::kPerAppTimeLimitsPolicy,
329 base::BindRepeating(&AppTimeController::TimeLimitsPolicyUpdated,
330 base::Unretained(this)));
331 pref_registrar_->Add(
332 prefs::kPerAppTimeLimitsAllowlistPolicy,
333 base::BindRepeating(&AppTimeController::TimeLimitsAllowlistPolicyUpdated,
334 base::Unretained(this)));
335 }
336
TimeLimitsPolicyUpdated(const std::string & pref_name)337 void AppTimeController::TimeLimitsPolicyUpdated(const std::string& pref_name) {
338 DCHECK_EQ(pref_name, prefs::kPerAppTimeLimitsPolicy);
339
340 const base::Value* policy =
341 pref_registrar_->prefs()->GetDictionary(prefs::kPerAppTimeLimitsPolicy);
342
343 if (!policy || !policy->is_dict()) {
344 LOG(WARNING) << "Invalid PerAppTimeLimits policy.";
345 return;
346 }
347 std::map<AppId, AppLimit> app_limits = policy::AppLimitsFromDict(*policy);
348
349 // If web time limit feature is not enabled, then remove chrome's time limit
350 // from here.
351 if (!WebTimeLimitEnforcer::IsEnabled() &&
352 base::Contains(app_limits, GetChromeAppId())) {
353 app_limits.erase(GetChromeAppId());
354 }
355
356 bool updated = app_registry_->UpdateAppLimits(app_limits);
357
358 app_registry_->SetReportingEnabled(
359 policy::ActivityReportingEnabledFromDict(*policy));
360
361 base::Optional<base::TimeDelta> new_reset_time =
362 policy::ResetTimeFromDict(*policy);
363 // TODO(agawronska): Propagate the information about reset time change.
364 if (new_reset_time && *new_reset_time != limits_reset_time_)
365 limits_reset_time_ = *new_reset_time;
366
367 apps_with_limit_ =
368 app_registry_->GetAppsWithAppRestriction(AppRestriction::kTimeLimit)
369 .size();
370
371 if (updated) {
372 patl_policy_update_count_++;
373
374 base::UmaHistogramCounts1000(kAppsWithTimeLimitMetric, apps_with_limit_);
375
376 int blocked_apps =
377 app_registry_->GetAppsWithAppRestriction(AppRestriction::kBlocked)
378 .size();
379
380 base::UmaHistogramCounts1000(kBlockedAppsCountMetric, blocked_apps);
381 }
382 }
383
TimeLimitsAllowlistPolicyUpdated(const std::string & pref_name)384 void AppTimeController::TimeLimitsAllowlistPolicyUpdated(
385 const std::string& pref_name) {
386 DCHECK_EQ(pref_name, prefs::kPerAppTimeLimitsAllowlistPolicy);
387
388 const base::DictionaryValue* policy = pref_registrar_->prefs()->GetDictionary(
389 prefs::kPerAppTimeLimitsAllowlistPolicy);
390
391 // Figure out a way to avoid cloning
392 AppTimeLimitsAllowlistPolicyWrapper wrapper(policy);
393
394 app_registry_->OnTimeLimitAllowlistChanged(wrapper);
395
396 if (web_time_enforcer_)
397 web_time_enforcer_->OnTimeLimitAllowlistChanged(wrapper);
398 }
399
ShowAppTimeLimitNotification(const AppId & app_id,const base::Optional<base::TimeDelta> & time_limit,AppNotification notification)400 void AppTimeController::ShowAppTimeLimitNotification(
401 const AppId& app_id,
402 const base::Optional<base::TimeDelta>& time_limit,
403 AppNotification notification) {
404 DCHECK_NE(AppNotification::kUnknown, notification);
405
406 if (notification == AppNotification::kTimeLimitReached)
407 return;
408
409 const std::string app_name = app_service_wrapper_->GetAppName(app_id);
410 int size_hint_in_dp = 48;
411 app_service_wrapper_->GetAppIcon(
412 app_id, size_hint_in_dp,
413 base::BindOnce(&AppTimeController::ShowNotificationForApp,
414 weak_ptr_factory_.GetWeakPtr(), app_name, notification,
415 time_limit));
416 }
417
OnAppLimitReached(const AppId & app_id,base::TimeDelta time_limit,bool was_active)418 void AppTimeController::OnAppLimitReached(const AppId& app_id,
419 base::TimeDelta time_limit,
420 bool was_active) {
421 bool show_dialog = was_active;
422 if (app_id == GetChromeAppId() || IsAppOpenedInChrome(app_id, profile_))
423 show_dialog = false;
424
425 app_service_wrapper_->PauseApp(PauseAppInfo(app_id, time_limit, show_dialog));
426
427 // TODO(crbug/1074516) This is a temporary workaround. The underlying problem
428 // should be fixed.
429 if (app_id == GetChromeAppId() && web_time_enforcer_)
430 web_time_enforcer_->OnWebTimeLimitReached(time_limit);
431 }
432
OnAppLimitRemoved(const AppId & app_id)433 void AppTimeController::OnAppLimitRemoved(const AppId& app_id) {
434 app_service_wrapper_->ResumeApp(app_id);
435
436 // TODO(crbug/1074516) This is a temporary workaround. The underlying problem
437 // should be fixed.
438 if (app_id == GetChromeAppId() && web_time_enforcer_)
439 web_time_enforcer_->OnWebTimeLimitEnded();
440 }
441
OnAppInstalled(const AppId & app_id)442 void AppTimeController::OnAppInstalled(const AppId& app_id) {
443 if (!WebTimeLimitEnforcer::IsEnabled() && IsWebAppOrExtension(app_id))
444 return;
445
446 const base::Value* allowlist_policy = pref_registrar_->prefs()->GetDictionary(
447 prefs::kPerAppTimeLimitsAllowlistPolicy);
448 if (allowlist_policy && allowlist_policy->is_dict()) {
449 AppTimeLimitsAllowlistPolicyWrapper wrapper(allowlist_policy);
450 if (base::Contains(wrapper.GetAllowlistAppList(), app_id))
451 app_registry_->SetAppAllowlisted(app_id);
452 } else {
453 LOG(WARNING) << " Invalid PerAppTimeLimitAllowlist policy";
454 }
455
456 const base::Value* policy =
457 pref_registrar_->prefs()->GetDictionary(prefs::kPerAppTimeLimitsPolicy);
458
459 if (!policy || !policy->is_dict()) {
460 LOG(WARNING) << "Invalid PerAppTimeLimits policy.";
461 return;
462 }
463
464 // Update the application's time limit.
465 const std::map<AppId, AppLimit> limits = policy::AppLimitsFromDict(*policy);
466 // Update the limit for newly installed app, if it exists.
467 auto result = limits.find(app_id);
468 if (result == limits.end())
469 return;
470
471 app_registry_->SetAppLimit(result->first, result->second);
472 }
473
GetNextResetTime() const474 base::Time AppTimeController::GetNextResetTime() const {
475 // UTC time now.
476 base::Time now = base::Time::Now();
477
478 // UTC time local midnight.
479 base::Time nearest_midnight = now.LocalMidnight();
480
481 base::Time prev_midnight;
482 if (now > nearest_midnight)
483 prev_midnight = nearest_midnight;
484 else
485 prev_midnight = nearest_midnight - base::TimeDelta::FromHours(24);
486
487 base::Time next_reset_time = prev_midnight + limits_reset_time_;
488
489 if (next_reset_time > now)
490 return next_reset_time;
491
492 // We have already reset for this day. The reset time is the next day.
493 return next_reset_time + base::TimeDelta::FromHours(24);
494 }
495
ScheduleForTimeLimitReset()496 void AppTimeController::ScheduleForTimeLimitReset() {
497 if (reset_timer_.IsRunning())
498 reset_timer_.AbandonAndStop();
499
500 base::TimeDelta time_until_reset = GetNextResetTime() - base::Time::Now();
501 reset_timer_.Start(FROM_HERE, time_until_reset,
502 base::BindOnce(&AppTimeController::OnResetTimeReached,
503 base::Unretained(this)));
504 }
505
OnResetTimeReached()506 void AppTimeController::OnResetTimeReached() {
507 base::Time now = base::Time::Now();
508
509 app_registry_->OnResetTimeReached(now);
510
511 SetLastResetTime(now);
512
513 ScheduleForTimeLimitReset();
514 }
515
RestoreLastResetTime()516 void AppTimeController::RestoreLastResetTime() {
517 PrefService* pref_service = profile_->GetPrefs();
518 int64_t reset_time =
519 pref_service->GetInt64(prefs::kPerAppTimeLimitsLastResetTime);
520
521 if (reset_time == 0) {
522 SetLastResetTime(base::Time::Now());
523 } else {
524 last_limits_reset_time_ = base::Time::FromDeltaSinceWindowsEpoch(
525 base::TimeDelta::FromMicroseconds(reset_time));
526 }
527
528 if (HasTimeCrossedResetBoundary()) {
529 OnResetTimeReached();
530 } else {
531 ScheduleForTimeLimitReset();
532 }
533 }
534
SetLastResetTime(base::Time timestamp)535 void AppTimeController::SetLastResetTime(base::Time timestamp) {
536 // |timestamp| needs to be adjusted to ensure that it is happening at the time
537 // specified by policy.
538 const base::Time nearest_midnight = timestamp.LocalMidnight();
539 base::Time prev_midnight;
540 if (timestamp > nearest_midnight)
541 prev_midnight = nearest_midnight;
542 else
543 prev_midnight = nearest_midnight - base::TimeDelta::FromHours(24);
544
545 base::Time reset_time = prev_midnight + limits_reset_time_;
546 if (reset_time <= timestamp)
547 last_limits_reset_time_ = reset_time;
548 else
549 last_limits_reset_time_ = reset_time - base::TimeDelta::FromHours(24);
550
551 PrefService* service = profile_->GetPrefs();
552 DCHECK(service);
553 service->SetInt64(
554 prefs::kPerAppTimeLimitsLastResetTime,
555 last_limits_reset_time_.ToDeltaSinceWindowsEpoch().InMicroseconds());
556 service->CommitPendingWrite();
557 }
558
HasTimeCrossedResetBoundary() const559 bool AppTimeController::HasTimeCrossedResetBoundary() const {
560 // Time after system time or timezone changed.
561 base::Time now = base::Time::Now();
562
563 return now < last_limits_reset_time_ || now >= kDay + last_limits_reset_time_;
564 }
565
OpenFamilyLinkApp()566 void AppTimeController::OpenFamilyLinkApp() {
567 const std::string app_id = arc::ArcPackageNameToAppId(
568 chromeos::ChildUserService::kFamilyLinkHelperAppPackageName, profile_);
569
570 if (app_service_wrapper_->IsAppInstalled(app_id)) {
571 // Launch Family Link Help app since it is available.
572 app_service_wrapper_->LaunchApp(app_id);
573 return;
574 }
575 // No Family Link Help app installed, so try to launch Play Store to Family
576 // Link Help app install page.
577 arc::LaunchPlayStoreWithUrl(
578 chromeos::ChildUserService::kFamilyLinkHelperAppPlayStoreURL);
579 }
580
ShowNotificationForApp(const std::string & app_name,AppNotification notification,base::Optional<base::TimeDelta> time_limit,base::Optional<gfx::ImageSkia> icon)581 void AppTimeController::ShowNotificationForApp(
582 const std::string& app_name,
583 AppNotification notification,
584 base::Optional<base::TimeDelta> time_limit,
585 base::Optional<gfx::ImageSkia> icon) {
586 DCHECK(notification == AppNotification::kFiveMinutes ||
587 notification == AppNotification::kOneMinute ||
588 notification == AppNotification::kTimeLimitChanged ||
589 notification == AppNotification::kBlocked ||
590 notification == AppNotification::kAvailable);
591
592 DCHECK(notification == AppNotification::kTimeLimitChanged ||
593 notification == AppNotification::kBlocked ||
594 notification == AppNotification::kAvailable || time_limit.has_value());
595
596 // Alright we have all the messages that we want.
597 const base::string16 app_name_16 = base::UTF8ToUTF16(app_name);
598 const base::string16 title =
599 GetNotificationTitleFor(app_name_16, notification);
600 const base::string16 message =
601 GetNotificationMessageFor(app_name_16, notification, time_limit);
602 // Family link display source.
603 const base::string16 notification_source =
604 l10n_util::GetStringUTF16(IDS_TIME_LIMIT_NOTIFICATION_DISPLAY_SOURCE);
605
606 std::string notification_id = GetNotificationIdFor(app_name, notification);
607 message_center::RichNotificationData option_fields;
608 option_fields.fullscreen_visibility =
609 message_center::FullscreenVisibility::OVER_USER;
610
611 std::unique_ptr<message_center::Notification> message_center_notification =
612 ash::CreateSystemNotification(
613 message_center::NOTIFICATION_TYPE_SIMPLE, notification_id, title,
614 message, notification_source, GURL(),
615 message_center::NotifierId(
616 message_center::NotifierType::SYSTEM_COMPONENT,
617 kFamilyLinkSourceId),
618 option_fields,
619 notification == AppNotification::kTimeLimitChanged
620 ? base::MakeRefCounted<
621 message_center::HandleNotificationClickDelegate>(
622 base::BindRepeating(&AppTimeController::OpenFamilyLinkApp,
623 weak_ptr_factory_.GetWeakPtr()))
624 : base::MakeRefCounted<message_center::NotificationDelegate>(),
625 chromeos::kNotificationSupervisedUserIcon,
626 message_center::SystemNotificationWarningLevel::NORMAL);
627
628 if (icon.has_value())
629 message_center_notification->set_icon(gfx::Image(icon.value()));
630
631 auto* notification_display_service =
632 NotificationDisplayService::GetForProfile(profile_);
633 if (!notification_display_service)
634 return;
635
636 // Close the existing notification with notification_id.
637 notification_display_service->Close(NotificationHandler::Type::TRANSIENT,
638 notification_id);
639
640 notification_display_service->Display(NotificationHandler::Type::TRANSIENT,
641 *message_center_notification,
642 /*metadata=*/nullptr);
643 }
644
645 } // namespace app_time
646 } // namespace chromeos
647