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