// Copyright 2012 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/ash/accessibility/magnification_manager.h"
#include <limits>
#include <memory>
#include "ash/accessibility/magnifier/docked_magnifier_controller.h"
#include "ash/accessibility/magnifier/fullscreen_magnifier_controller.h"
#include "ash/constants/ash_pref_names.h"
#include "ash/shell.h"
#include "base/functional/bind.h"
#include "chrome/browser/ash/accessibility/accessibility_manager.h"
#include "chrome/browser/profiles/profile_manager.h"
#include "chromeos/ash/components/browser_context_helper/browser_context_helper.h"
#include "chromeos/ash/components/browser_context_helper/browser_context_types.h"
#include "components/prefs/pref_change_registrar.h"
#include "components/prefs/pref_service.h"
#include "components/user_manager/user_manager.h"
#include "content/public/browser/focused_node_details.h"
#include "ui/accessibility/accessibility_features.h"
#include "ui/accessibility/ax_role_properties.h"
#include "ui/gfx/geometry/point.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/views/accessibility/ax_event_manager.h"
#include "ui/views/accessibility/view_accessibility.h"
namespace ash {
namespace {
MagnificationManager* g_magnification_manager = nullptr;
} // namespace
// static
void MagnificationManager::Initialize() {
CHECK(g_magnification_manager == nullptr);
g_magnification_manager = new MagnificationManager();
}
// static
void MagnificationManager::Shutdown() {
CHECK(g_magnification_manager);
delete g_magnification_manager;
g_magnification_manager = nullptr;
}
// static
MagnificationManager* MagnificationManager::Get() {
return g_magnification_manager;
}
bool MagnificationManager::IsMagnifierEnabled() const {
return fullscreen_magnifier_enabled_;
}
void MagnificationManager::SetMagnifierEnabled(bool enabled) {
if (!profile_)
return;
PrefService* prefs = profile_->GetPrefs();
prefs->SetBoolean(prefs::kAccessibilityScreenMagnifierEnabled, enabled);
prefs->CommitPendingWrite();
}
bool MagnificationManager::IsDockedMagnifierEnabled() const {
return profile_ &&
profile_->GetPrefs()->GetBoolean(prefs::kDockedMagnifierEnabled);
}
void MagnificationManager::SetDockedMagnifierEnabled(bool enabled) {
if (!profile_)
return;
PrefService* prefs = profile_->GetPrefs();
prefs->SetBoolean(prefs::kDockedMagnifierEnabled, enabled);
prefs->CommitPendingWrite();
}
void MagnificationManager::SaveScreenMagnifierScale(double scale) {
if (!profile_)
return;
profile_->GetPrefs()->SetDouble(prefs::kAccessibilityScreenMagnifierScale,
scale);
}
double MagnificationManager::GetSavedScreenMagnifierScale() const {
if (!profile_)
return std::numeric_limits<double>::min();
return profile_->GetPrefs()->GetDouble(
prefs::kAccessibilityScreenMagnifierScale);
}
void MagnificationManager::OnProfileWillBeDestroyed(Profile* profile) {
DCHECK_EQ(profile_, profile);
SetProfile(nullptr);
}
void MagnificationManager::HandleMoveMagnifierToRectIfEnabled(
const gfx::Rect& rect) {
// Fullscreen magnifier and docked magnifier are mutually exclusive.
if (fullscreen_magnifier_enabled_) {
Shell::Get()->fullscreen_magnifier_controller()->HandleMoveMagnifierToRect(
rect);
return;
}
if (IsDockedMagnifierEnabled())
Shell::Get()->docked_magnifier_controller()->MoveMagnifierToRect(rect);
}
void MagnificationManager::HandleMagnifierCenterOnPointIfEnabled(
const gfx::Point& point_in_screen) {
// Fullscreen magnifier and docked magnifier are mutually exclusive.
if (fullscreen_magnifier_enabled_) {
Shell::Get()->fullscreen_magnifier_controller()->CenterOnPoint(
point_in_screen);
return;
}
if (IsDockedMagnifierEnabled()) {
Shell::Get()->docked_magnifier_controller()->CenterOnPoint(point_in_screen);
}
}
void MagnificationManager::OnMouseEvent(ui::MouseEvent* event) {
last_mouse_event_ = base::TimeTicks::Now();
}
void MagnificationManager::OnViewEvent(views::View* view,
ax::mojom::Event event_type) {
if (!view) {
return;
}
if (!fullscreen_magnifier_enabled_ && !IsDockedMagnifierEnabled()) {
return;
}
if (event_type != ax::mojom::Event::kFocus &&
event_type != ax::mojom::Event::kSelection) {
return;
}
ui::AXNodeData data;
view->GetViewAccessibility().GetAccessibleNodeData(&data);
}
void MagnificationManager::SetProfileForTest(Profile* profile) {
SetProfile(profile);
}
MagnificationManager::MagnificationManager() {
session_observation_.Observe(session_manager::SessionManager::Get());
user_manager::UserManager::Get()->AddSessionStateObserver(this);
views::AXEventManager::Get()->AddObserver(this);
}
MagnificationManager::~MagnificationManager() {
CHECK(this == g_magnification_manager);
auto* event_manager = views::AXEventManager::Get();
if (event_manager) {
event_manager->RemoveObserver(this);
}
auto* user_manager = user_manager::UserManager::Get();
if (user_manager) {
user_manager->RemoveSessionStateObserver(this);
}
}
void MagnificationManager::OnLoginOrLockScreenVisible() {
// Update `profile_` when entering the login screen.
Profile* profile = ProfileManager::GetActiveUserProfile();
if (IsSigninBrowserContext(profile)) {
SetProfile(profile);
}
}
void MagnificationManager::ActiveUserChanged(user_manager::User* active_user) {
if (!active_user)
return;
active_user->AddProfileCreatedObserver(
base::BindOnce(&MagnificationManager::SetProfileByUser,
weak_ptr_factory_.GetWeakPtr(), active_user));
}
void MagnificationManager::SetProfileByUser(const user_manager::User* user) {
SetProfile(Profile::FromBrowserContext(
BrowserContextHelper::Get()->GetBrowserContextByUser(user)));
}
void MagnificationManager::SetProfile(Profile* profile) {
if (profile_) {
DCHECK(profile_observation_.IsObservingSource(profile_.get()));
profile_observation_.Reset();
}
DCHECK(!profile_observation_.IsObserving());
pref_change_registrar_.reset();
if (profile) {
pref_change_registrar_ = std::make_unique<PrefChangeRegistrar>();
pref_change_registrar_->Init(profile->GetPrefs());
pref_change_registrar_->Add(
prefs::kAccessibilityScreenMagnifierEnabled,
base::BindRepeating(&MagnificationManager::UpdateMagnifierFromPrefs,
base::Unretained(this)));
pref_change_registrar_->Add(
prefs::kAccessibilityScreenMagnifierScale,
base::BindRepeating(&MagnificationManager::UpdateMagnifierFromPrefs,
base::Unretained(this)));
pref_change_registrar_->Add(
prefs::kAccessibilityScreenMagnifierMouseFollowingMode,
base::BindRepeating(&MagnificationManager::UpdateMagnifierFromPrefs,
base::Unretained(this)));
pref_change_registrar_->Add(
prefs::kDockedMagnifierEnabled,
base::BindRepeating(
&MagnificationManager::UpdateDockedMagnifierFromPrefs,
base::Unretained(this)));
profile_observation_.Observe(profile);
}
profile_ = profile;
UpdateMagnifierFromPrefs();
UpdateDockedMagnifierFromPrefs();
}
void MagnificationManager::SetMagnifierEnabledInternal(bool enabled) {
// This method may be invoked even when the other magnifier settings (e.g.
// type or scale) are changed, so we need to call magnification controller
// even if |enabled| is unchanged. Only if |enabled| is false and the
// magnifier is already disabled, we are sure that we don't need to reflect
// the new settings right now because the magnifier keeps disabled.
if (!enabled && !fullscreen_magnifier_enabled_)
return;
fullscreen_magnifier_enabled_ = enabled;
Shell::Get()->fullscreen_magnifier_controller()->SetEnabled(enabled);
}
void MagnificationManager::SetMagnifierScaleInternal(double scale) {
if (scale_ == scale)
return;
scale_ = scale;
Shell::Get()->fullscreen_magnifier_controller()->SetScale(
scale_, false /* animate */);
}
void MagnificationManager::SetMagnifierMouseFollowingModeInternal(
MagnifierMouseFollowingMode mouse_following_mode) {
Shell::Get()->fullscreen_magnifier_controller()->set_mouse_following_mode(
mouse_following_mode);
}
void MagnificationManager::UpdateMagnifierFromPrefs() {
if (!profile_)
return;
PrefService* prefs = profile_->GetPrefs();
const bool enabled =
prefs->GetBoolean(prefs::kAccessibilityScreenMagnifierEnabled);
const double scale =
prefs->GetDouble(prefs::kAccessibilityScreenMagnifierScale);
const MagnifierMouseFollowingMode mouse_following_mode =
static_cast<MagnifierMouseFollowingMode>(prefs->GetInteger(
prefs::kAccessibilityScreenMagnifierMouseFollowingMode));
SetMagnifierMouseFollowingModeInternal(mouse_following_mode);
SetMagnifierScaleInternal(scale);
SetMagnifierEnabledInternal(enabled);
AccessibilityStatusEventDetails details(
AccessibilityNotificationType::kToggleScreenMagnifier,
fullscreen_magnifier_enabled_);
if (!AccessibilityManager::Get())
return;
AccessibilityManager::Get()->NotifyAccessibilityStatusChanged(details);
if (Shell::Get())
Shell::Get()->UpdateCursorCompositingEnabled();
}
void MagnificationManager::UpdateDockedMagnifierFromPrefs() {
if (!profile_)
return;
PrefService* prefs = profile_->GetPrefs();
const bool enabled = prefs->GetBoolean(prefs::kDockedMagnifierEnabled);
AccessibilityStatusEventDetails details(
AccessibilityNotificationType::kToggleDockedMagnifier, enabled);
if (!AccessibilityManager::Get())
return;
AccessibilityManager::Get()->NotifyAccessibilityStatusChanged(details);
}
} // namespace ash