1// Copyright (c) 2012 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/accessibility/magnification_manager.h"
6
7#include <limits>
8
9#include "ash/magnifier/magnification_controller.h"
10#include "ash/magnifier/partial_magnification_controller.h"
11#include "ash/session_state_delegate.h"
12#include "ash/shell.h"
13#include "ash/shell_delegate.h"
14#include "ash/system/tray/system_tray_notifier.h"
15#include "base/memory/scoped_ptr.h"
16#include "base/memory/singleton.h"
17#include "base/prefs/pref_member.h"
18#include "base/prefs/pref_service.h"
19#include "chrome/browser/chrome_notification_types.h"
20#include "chrome/browser/chromeos/accessibility/accessibility_manager.h"
21#include "chrome/browser/chromeos/login/user_manager.h"
22#include "chrome/browser/chromeos/profiles/profile_helper.h"
23#include "chrome/browser/profiles/profile.h"
24#include "chrome/browser/profiles/profile_manager.h"
25#include "chrome/common/pref_names.h"
26#include "content/public/browser/notification_details.h"
27#include "content/public/browser/notification_observer.h"
28#include "content/public/browser/notification_registrar.h"
29#include "content/public/browser/notification_service.h"
30#include "content/public/browser/notification_source.h"
31
32namespace chromeos {
33
34namespace {
35static MagnificationManager* g_magnification_manager = NULL;
36}
37
38class MagnificationManagerImpl : public MagnificationManager,
39                                 public content::NotificationObserver,
40                                 public ash::SessionStateObserver {
41 public:
42  MagnificationManagerImpl()
43      : first_time_update_(true),
44        profile_(NULL),
45        magnifier_enabled_pref_handler_(prefs::kScreenMagnifierEnabled),
46        magnifier_type_pref_handler_(prefs::kScreenMagnifierType),
47        magnifier_scale_pref_handler_(prefs::kScreenMagnifierScale),
48        type_(ash::kDefaultMagnifierType),
49        enabled_(false) {
50    registrar_.Add(this,
51                   chrome::NOTIFICATION_LOGIN_OR_LOCK_WEBUI_VISIBLE,
52                   content::NotificationService::AllSources());
53    registrar_.Add(this,
54                   chrome::NOTIFICATION_SESSION_STARTED,
55                   content::NotificationService::AllSources());
56    registrar_.Add(this,
57                   chrome::NOTIFICATION_PROFILE_DESTROYED,
58                   content::NotificationService::AllSources());
59  }
60
61  virtual ~MagnificationManagerImpl() {
62    CHECK(this == g_magnification_manager);
63  }
64
65  // MagnificationManager implimentation:
66  virtual bool IsMagnifierEnabled() const OVERRIDE {
67    return enabled_;
68  }
69
70  virtual ash::MagnifierType GetMagnifierType() const OVERRIDE {
71    return type_;
72  }
73
74  virtual void SetMagnifierEnabled(bool enabled) OVERRIDE {
75    if (!profile_)
76      return;
77
78    PrefService* prefs = profile_->GetPrefs();
79    prefs->SetBoolean(prefs::kScreenMagnifierEnabled, enabled);
80    prefs->CommitPendingWrite();
81  }
82
83  virtual void SetMagnifierType(ash::MagnifierType type) OVERRIDE {
84    if (!profile_)
85      return;
86
87    PrefService* prefs = profile_->GetPrefs();
88    prefs->SetInteger(prefs::kScreenMagnifierType, type);
89    prefs->CommitPendingWrite();
90  }
91
92  virtual void SaveScreenMagnifierScale(double scale) OVERRIDE {
93    if (!profile_)
94      return;
95
96    profile_->GetPrefs()->SetDouble(prefs::kScreenMagnifierScale, scale);
97  }
98
99  virtual double GetSavedScreenMagnifierScale() const OVERRIDE {
100    if (!profile_)
101      return std::numeric_limits<double>::min();
102
103    return profile_->GetPrefs()->GetDouble(prefs::kScreenMagnifierScale);
104  }
105
106  virtual void SetProfileForTest(Profile* profile) OVERRIDE {
107    SetProfile(profile);
108  }
109
110  // SessionStateObserver overrides:
111  virtual void ActiveUserChanged(const std::string& user_id) OVERRIDE {
112    SetProfile(ProfileManager::GetActiveUserProfile());
113  }
114
115 private:
116  void SetProfile(Profile* profile) {
117    pref_change_registrar_.reset();
118
119    if (profile) {
120      // TODO(yoshiki): Move following code to PrefHandler.
121      pref_change_registrar_.reset(new PrefChangeRegistrar);
122      pref_change_registrar_->Init(profile->GetPrefs());
123      pref_change_registrar_->Add(
124          prefs::kScreenMagnifierEnabled,
125          base::Bind(&MagnificationManagerImpl::UpdateMagnifierFromPrefs,
126                     base::Unretained(this)));
127      pref_change_registrar_->Add(
128          prefs::kScreenMagnifierType,
129          base::Bind(&MagnificationManagerImpl::UpdateMagnifierFromPrefs,
130                     base::Unretained(this)));
131    }
132
133    magnifier_enabled_pref_handler_.HandleProfileChanged(profile_, profile);
134    magnifier_type_pref_handler_.HandleProfileChanged(profile_, profile);
135    magnifier_scale_pref_handler_.HandleProfileChanged(profile_, profile);
136
137    profile_ = profile;
138    UpdateMagnifierFromPrefs();
139  }
140
141  virtual void SetMagnifierEnabledInternal(bool enabled) {
142    // This method may be invoked even when the other magnifier settings (e.g.
143    // type or scale) are changed, so we need to call magnification controller
144    // even if |enabled| is unchanged. Only if |enabled| is false and the
145    // magnifier is already disabled, we are sure that we don't need to reflect
146    // the new settings right now because the magnifier keeps disabled.
147    if (!enabled && !enabled_)
148      return;
149
150    enabled_ = enabled;
151
152    if (type_ == ash::MAGNIFIER_FULL) {
153      ash::Shell::GetInstance()->magnification_controller()->SetEnabled(
154          enabled_);
155    } else {
156      ash::Shell::GetInstance()->partial_magnification_controller()->SetEnabled(
157          enabled_);
158    }
159  }
160
161  virtual void SetMagnifierTypeInternal(ash::MagnifierType type) {
162    if (type_ == type)
163      return;
164
165    type_ = ash::MAGNIFIER_FULL;  // (leave out for full magnifier)
166  }
167
168  void UpdateMagnifierFromPrefs() {
169    if (!profile_)
170      return;
171
172    const bool enabled =
173        profile_->GetPrefs()->GetBoolean(prefs::kScreenMagnifierEnabled);
174    const int type_integer =
175        profile_->GetPrefs()->GetInteger(prefs::kScreenMagnifierType);
176
177    ash::MagnifierType type = ash::kDefaultMagnifierType;
178    if (type_integer > 0 && type_integer <= ash::kMaxMagnifierType) {
179      type = static_cast<ash::MagnifierType>(type_integer);
180    } else if (type_integer == 0) {
181      // Type 0 is used to disable the screen magnifier through policy. As the
182      // magnifier type is irrelevant in this case, it is OK to just fall back
183      // to the default.
184    } else {
185      NOTREACHED();
186    }
187
188    if (!enabled) {
189      SetMagnifierEnabledInternal(enabled);
190      SetMagnifierTypeInternal(type);
191    } else {
192      SetMagnifierTypeInternal(type);
193      SetMagnifierEnabledInternal(enabled);
194    }
195
196    AccessibilityStatusEventDetails details(
197        enabled_, type_, ash::A11Y_NOTIFICATION_NONE);
198    content::NotificationService::current()->Notify(
199        chrome::NOTIFICATION_CROS_ACCESSIBILITY_TOGGLE_SCREEN_MAGNIFIER,
200        content::NotificationService::AllSources(),
201        content::Details<AccessibilityStatusEventDetails>(&details));
202  }
203
204  // content::NotificationObserver implementation:
205  virtual void Observe(int type,
206                       const content::NotificationSource& source,
207                       const content::NotificationDetails& details) OVERRIDE {
208    switch (type) {
209      case chrome::NOTIFICATION_LOGIN_OR_LOCK_WEBUI_VISIBLE: {
210        // Update |profile_| when entering the login screen.
211        Profile* profile = ProfileManager::GetDefaultProfile();
212        if (ProfileHelper::IsSigninProfile(profile))
213          SetProfile(profile);
214        break;
215      }
216      case chrome::NOTIFICATION_SESSION_STARTED:
217        // Update |profile_| when entering a session.
218        SetProfile(ProfileManager::GetDefaultProfile());
219
220        // Add a session state observer to be able to monitor session changes.
221        if (!session_state_observer_.get() && ash::Shell::HasInstance())
222          session_state_observer_.reset(
223              new ash::ScopedSessionStateObserver(this));
224        break;
225      case chrome::NOTIFICATION_PROFILE_DESTROYED: {
226        // Update |profile_| when exiting a session or shutting down.
227        Profile* profile = content::Source<Profile>(source).ptr();
228        if (profile_ == profile)
229          SetProfile(NULL);
230        break;
231      }
232    }
233  }
234
235  bool first_time_update_;
236  Profile* profile_;
237
238  AccessibilityManager::PrefHandler magnifier_enabled_pref_handler_;
239  AccessibilityManager::PrefHandler magnifier_type_pref_handler_;
240  AccessibilityManager::PrefHandler magnifier_scale_pref_handler_;
241
242  ash::MagnifierType type_;
243  bool enabled_;
244
245  content::NotificationRegistrar registrar_;
246  scoped_ptr<PrefChangeRegistrar> pref_change_registrar_;
247  scoped_ptr<ash::ScopedSessionStateObserver> session_state_observer_;
248
249  DISALLOW_COPY_AND_ASSIGN(MagnificationManagerImpl);
250};
251
252// static
253void MagnificationManager::Initialize() {
254  CHECK(g_magnification_manager == NULL);
255  g_magnification_manager = new MagnificationManagerImpl();
256}
257
258// static
259void MagnificationManager::Shutdown() {
260  CHECK(g_magnification_manager);
261  delete g_magnification_manager;
262  g_magnification_manager = NULL;
263}
264
265// static
266MagnificationManager* MagnificationManager::Get() {
267  return g_magnification_manager;
268}
269
270}  // namespace chromeos
271