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