1// Copyright 2013 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/policy/recommendation_restorer.h"
6
7#include "ash/shell.h"
8#include "base/bind.h"
9#include "base/bind_helpers.h"
10#include "base/location.h"
11#include "base/logging.h"
12#include "base/prefs/pref_service.h"
13#include "base/time/time.h"
14#include "base/values.h"
15#include "chrome/browser/chrome_notification_types.h"
16#include "chrome/browser/chromeos/profiles/profile_helper.h"
17#include "chrome/browser/profiles/profile.h"
18#include "chrome/common/pref_names.h"
19#include "content/public/browser/notification_details.h"
20#include "content/public/browser/notification_service.h"
21#include "content/public/browser/notification_source.h"
22#include "ui/wm/core/user_activity_detector.h"
23
24namespace policy {
25
26namespace {
27// The amount of idle time after which recommended values are restored.
28const int kRestoreDelayInMs = 60 * 1000;  // 1 minute.
29}  // namespace
30
31RecommendationRestorer::RecommendationRestorer(Profile* profile)
32    : logged_in_(false) {
33  if (!chromeos::ProfileHelper::IsSigninProfile(profile))
34    return;
35
36  pref_change_registrar_.Init(profile->GetPrefs());
37  pref_change_registrar_.Add(
38      prefs::kAccessibilityLargeCursorEnabled,
39      base::Bind(
40          &RecommendationRestorer::Restore, base::Unretained(this), true));
41  pref_change_registrar_.Add(
42      prefs::kAccessibilitySpokenFeedbackEnabled,
43      base::Bind(
44          &RecommendationRestorer::Restore, base::Unretained(this), true));
45  pref_change_registrar_.Add(
46      prefs::kAccessibilityHighContrastEnabled,
47      base::Bind(
48          &RecommendationRestorer::Restore, base::Unretained(this), true));
49  pref_change_registrar_.Add(
50      prefs::kAccessibilityScreenMagnifierEnabled,
51      base::Bind(
52          &RecommendationRestorer::Restore, base::Unretained(this), true));
53  pref_change_registrar_.Add(
54      prefs::kAccessibilityScreenMagnifierType,
55      base::Bind(
56          &RecommendationRestorer::Restore, base::Unretained(this), true));
57  pref_change_registrar_.Add(
58      prefs::kAccessibilityVirtualKeyboardEnabled,
59      base::Bind(
60          &RecommendationRestorer::Restore, base::Unretained(this), true));
61
62  notification_registrar_.Add(this, chrome::NOTIFICATION_LOGIN_USER_CHANGED,
63                              content::NotificationService::AllSources());
64
65  RestoreAll();
66}
67
68RecommendationRestorer::~RecommendationRestorer() {
69}
70
71void RecommendationRestorer::Shutdown() {
72  StopTimer();
73  pref_change_registrar_.RemoveAll();
74  notification_registrar_.RemoveAll();
75}
76
77void RecommendationRestorer::Observe(
78    int type,
79    const content::NotificationSource& source,
80    const content::NotificationDetails& details) {
81  if (type == chrome::NOTIFICATION_LOGIN_USER_CHANGED) {
82    logged_in_ = true;
83    notification_registrar_.RemoveAll();
84    StopTimer();
85    RestoreAll();
86  } else {
87    NOTREACHED();
88  }
89}
90
91void RecommendationRestorer::OnUserActivity(const ui::Event* event) {
92  if (restore_timer_.IsRunning())
93    restore_timer_.Reset();
94}
95
96void RecommendationRestorer::Restore(bool allow_delay,
97                                     const std::string& pref_name) {
98  const PrefService::Preference* pref =
99      pref_change_registrar_.prefs()->FindPreference(pref_name.c_str());
100  if (!pref) {
101    NOTREACHED();
102    return;
103  }
104
105  if (!pref->GetRecommendedValue() || !pref->HasUserSetting())
106    return;
107
108  if (logged_in_) {
109    allow_delay = false;
110  } else if (allow_delay && ash::Shell::HasInstance()) {
111    // Skip the delay if there has been no user input since the browser started.
112    const wm::UserActivityDetector* user_activity_detector =
113        ash::Shell::GetInstance()->user_activity_detector();
114    allow_delay = !user_activity_detector->last_activity_time().is_null();
115  }
116
117  if (allow_delay)
118    StartTimer();
119  else
120    pref_change_registrar_.prefs()->ClearPref(pref->name().c_str());
121}
122
123void RecommendationRestorer::RestoreAll() {
124  Restore(false, prefs::kAccessibilityLargeCursorEnabled);
125  Restore(false, prefs::kAccessibilitySpokenFeedbackEnabled);
126  Restore(false, prefs::kAccessibilityHighContrastEnabled);
127  Restore(false, prefs::kAccessibilityScreenMagnifierEnabled);
128  Restore(false, prefs::kAccessibilityScreenMagnifierType);
129  Restore(false, prefs::kAccessibilityVirtualKeyboardEnabled);
130}
131
132void RecommendationRestorer::StartTimer() {
133  // Listen for user activity so that the timer can be reset while the user is
134  // active, causing it to fire only when the user remains idle for
135  // |kRestoreDelayInMs|.
136  if (ash::Shell::HasInstance()) {
137    wm::UserActivityDetector* user_activity_detector =
138        ash::Shell::GetInstance()->user_activity_detector();
139    if (!user_activity_detector->HasObserver(this))
140      user_activity_detector->AddObserver(this);
141  }
142
143  // There should be a separate timer for each pref. However, in the common
144  // case of the user changing settings, a single timer is sufficient. This is
145  // because a change initiated by the user implies user activity, so that even
146  // if there was a separate timer per pref, they would all be reset at that
147  // point, causing them to fire at exactly the same time. In the much rarer
148  // case of a recommended value changing, a single timer is a close
149  // approximation of the behavior that would be obtained by resetting the timer
150  // for the affected pref only.
151  restore_timer_.Start(FROM_HERE,
152                       base::TimeDelta::FromMilliseconds(kRestoreDelayInMs),
153                       base::Bind(&RecommendationRestorer::RestoreAll,
154                                  base::Unretained(this)));
155}
156
157void RecommendationRestorer::StopTimer() {
158  restore_timer_.Stop();
159  if (ash::Shell::HasInstance())
160    ash::Shell::GetInstance()->user_activity_detector()->RemoveObserver(this);
161}
162
163}  // namespace policy
164