1 // Copyright 2020 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 "ash/system/accessibility/floating_accessibility_controller.h"
6
7 #include "ash/accessibility/accessibility_controller_impl.h"
8 #include "ash/public/cpp/shell_window_ids.h"
9 #include "ash/session/session_controller_impl.h"
10 #include "ash/shell.h"
11 #include "ash/strings/grit/ash_strings.h"
12 #include "ash/system/accessibility/floating_menu_utils.h"
13 #include "ash/system/tray/tray_background_view.h"
14 #include "ash/system/tray/tray_constants.h"
15 #include "ash/wm/collision_detection/collision_detection_utils.h"
16 #include "ash/wm/work_area_insets.h"
17 #include "base/check.h"
18 #include "base/notreached.h"
19 #include "ui/base/l10n/l10n_util.h"
20 #include "ui/compositor/scoped_layer_animation_settings.h"
21
22 namespace ash {
23
24 namespace {
25
26 constexpr int kFloatingMenuHeight = 64;
27 constexpr base::TimeDelta kAnimationDuration =
28 base::TimeDelta::FromMilliseconds(150);
29
30 } // namespace
31
FloatingAccessibilityController(AccessibilityControllerImpl * accessibility_controller)32 FloatingAccessibilityController::FloatingAccessibilityController(
33 AccessibilityControllerImpl* accessibility_controller)
34 : accessibility_controller_(accessibility_controller) {
35 Shell::Get()->locale_update_controller()->AddObserver(this);
36 accessibility_controller_->AddObserver(this);
37 }
~FloatingAccessibilityController()38 FloatingAccessibilityController::~FloatingAccessibilityController() {
39 Shell::Get()->locale_update_controller()->RemoveObserver(this);
40 accessibility_controller_->RemoveObserver(this);
41 if (bubble_widget_ && !bubble_widget_->IsClosed())
42 bubble_widget_->CloseNow();
43 }
44
Show(FloatingMenuPosition position)45 void FloatingAccessibilityController::Show(FloatingMenuPosition position) {
46 // Kiosk check.
47 if (!Shell::Get()->session_controller()->IsRunningInAppMode()) {
48 NOTREACHED()
49 << "Floating accessibility menu can only be run in a kiosk session.";
50 return;
51 }
52
53 DCHECK(!bubble_view_);
54
55 TrayBubbleView::InitParams init_params;
56 init_params.delegate = this;
57 // Our view uses SettingsBubbleContainer since it is activatable and is
58 // included in the collision detection logic.
59 init_params.parent_window = Shell::GetContainer(
60 Shell::GetPrimaryRootWindow(), kShellWindowId_SettingBubbleContainer);
61 init_params.anchor_mode = TrayBubbleView::AnchorMode::kRect;
62 // The widget's shadow is drawn below and on the sides of the view, with a
63 // width of kCollisionWindowWorkAreaInsetsDp. Set the top inset to 0 to ensure
64 // the detailed view is drawn at kCollisionWindowWorkAreaInsetsDp above the
65 // bubble menu when the position is at the bottom of the screen. The space
66 // between the bubbles belongs to the detailed view bubble's shadow.
67 init_params.insets = gfx::Insets(0, kCollisionWindowWorkAreaInsetsDp,
68 kCollisionWindowWorkAreaInsetsDp,
69 kCollisionWindowWorkAreaInsetsDp);
70 init_params.corner_radius = kUnifiedTrayCornerRadius;
71 init_params.has_shadow = false;
72 init_params.max_height = kFloatingMenuHeight;
73 init_params.translucent = true;
74 init_params.close_on_deactivate = false;
75 bubble_view_ = new FloatingAccessibilityBubbleView(init_params);
76
77 menu_view_ = new FloatingAccessibilityView(this);
78 menu_view_->SetBorder(
79 views::CreateEmptyBorder(kUnifiedTopShortcutSpacing, 0, 0, 0));
80 bubble_view_->AddChildView(menu_view_);
81 bubble_view_->SetFocusBehavior(
82 ActionableView::FocusBehavior::ACCESSIBLE_ONLY);
83
84 menu_view_->SetPaintToLayer();
85 menu_view_->layer()->SetFillsBoundsOpaquely(false);
86
87 bubble_widget_ = views::BubbleDialogDelegateView::CreateBubble(bubble_view_);
88 bubble_view_->SetCanActivate(true);
89 TrayBackgroundView::InitializeBubbleAnimations(bubble_widget_);
90 bubble_view_->InitializeAndShowBubble();
91
92 menu_view_->Initialize();
93
94 SetMenuPosition(position);
95 }
96
SetMenuPosition(FloatingMenuPosition new_position)97 void FloatingAccessibilityController::SetMenuPosition(
98 FloatingMenuPosition new_position) {
99 if (!menu_view_ || !bubble_view_ || !bubble_widget_)
100 return;
101
102 // Update the menu view's UX if the position has changed, or if it's not the
103 // default position (because that can change with language direction).
104 if (position_ != new_position ||
105 new_position == FloatingMenuPosition::kSystemDefault) {
106 menu_view_->SetMenuPosition(new_position);
107 }
108 position_ = new_position;
109
110 // If this is the default system position, pick the position based on the
111 // language direction.
112 if (new_position == FloatingMenuPosition::kSystemDefault)
113 new_position = DefaultSystemFloatingMenuPosition();
114
115 gfx::Rect new_bounds = GetOnScreenBoundsForFloatingMenuPosition(
116 menu_view_->GetPreferredSize(), new_position);
117
118 gfx::Rect resting_bounds =
119 CollisionDetectionUtils::AdjustToFitMovementAreaByGravity(
120 display::Screen::GetScreen()->GetDisplayNearestWindow(
121 bubble_widget_->GetNativeWindow()),
122 new_bounds);
123 // Un-inset the bounds to get the widget's bounds, which includes the drop
124 // shadow.
125 resting_bounds.Inset(-kCollisionWindowWorkAreaInsetsDp, 0,
126 -kCollisionWindowWorkAreaInsetsDp,
127 -kCollisionWindowWorkAreaInsetsDp);
128
129 if (bubble_widget_->GetWindowBoundsInScreen() == resting_bounds)
130 return;
131
132 ui::ScopedLayerAnimationSettings settings(
133 bubble_widget_->GetLayer()->GetAnimator());
134 settings.SetPreemptionStrategy(
135 ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET);
136 settings.SetTransitionDuration(kAnimationDuration);
137 settings.SetTweenType(gfx::Tween::EASE_OUT);
138 bubble_widget_->SetBounds(resting_bounds);
139
140 if (detailed_menu_controller_) {
141 detailed_menu_controller_->UpdateAnchorRect(
142 resting_bounds, GetAnchorAlignmentForFloatingMenuPosition(position_));
143 }
144 }
145
FocusOnMenu()146 void FloatingAccessibilityController::FocusOnMenu() {
147 bubble_view_->GetFocusManager()->ClearFocus();
148 bubble_view_->GetFocusManager()->AdvanceFocus(false /* reverse */);
149 }
150
OnDetailedMenuEnabled(bool enabled)151 void FloatingAccessibilityController::OnDetailedMenuEnabled(bool enabled) {
152 if (enabled) {
153 detailed_menu_controller_ =
154 std::make_unique<FloatingAccessibilityDetailedController>(this);
155 gfx::Rect anchor_rect = bubble_view_->GetBoundsInScreen();
156 anchor_rect.Inset(-kCollisionWindowWorkAreaInsetsDp, 0,
157 -kCollisionWindowWorkAreaInsetsDp,
158 -kCollisionWindowWorkAreaInsetsDp);
159 detailed_menu_controller_->Show(
160 anchor_rect, GetAnchorAlignmentForFloatingMenuPosition(position_));
161 menu_view_->SetDetailedViewShown(true);
162 } else {
163 detailed_menu_controller_.reset();
164 // We may need to update the autoclick bounds.
165 Shell::Get()
166 ->accessibility_controller()
167 ->UpdateAutoclickMenuBoundsIfNeeded();
168 }
169 }
170
OnLayoutChanged()171 void FloatingAccessibilityController::OnLayoutChanged() {
172 if (on_layout_change_)
173 on_layout_change_.Run();
174 SetMenuPosition(position_);
175 }
176
OnDetailedMenuClosed()177 void FloatingAccessibilityController::OnDetailedMenuClosed() {
178 detailed_menu_controller_.reset();
179
180 if (!menu_view_)
181 return;
182 menu_view_->SetDetailedViewShown(false);
183 if (bubble_widget_->IsActive())
184 menu_view_->FocusOnDetailedViewButton();
185 }
186
GetBubbleWidget()187 views::Widget* FloatingAccessibilityController::GetBubbleWidget() {
188 return bubble_widget_;
189 }
190
BubbleViewDestroyed()191 void FloatingAccessibilityController::BubbleViewDestroyed() {
192 bubble_view_ = nullptr;
193 bubble_widget_ = nullptr;
194 menu_view_ = nullptr;
195 }
196
GetAccessibleNameForBubble()197 base::string16 FloatingAccessibilityController::GetAccessibleNameForBubble() {
198 return l10n_util::GetStringUTF16(IDS_ASH_FLOATING_ACCESSIBILITY_MAIN_MENU);
199 }
200
OnLocaleChanged()201 void FloatingAccessibilityController::OnLocaleChanged() {
202 // Layout update is needed when language changes between LTR and RTL, if the
203 // position is the system default.
204 if (position_ == FloatingMenuPosition::kSystemDefault)
205 SetMenuPosition(position_);
206 }
207
OnAccessibilityStatusChanged()208 void FloatingAccessibilityController::OnAccessibilityStatusChanged() {
209 // Some features may change the available screen area(docked magnifier), we
210 // will update the location of the menu in such cases.
211 SetMenuPosition(position_);
212 if (detailed_menu_controller_)
213 detailed_menu_controller_->OnAccessibilityStatusChanged();
214 }
215
216 } // namespace ash
217