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_view.h"
6 
7 #include "ash/accessibility/accessibility_controller_impl.h"
8 #include "ash/keyboard/ui/keyboard_ui_controller.h"
9 #include "ash/public/cpp/system_tray.h"
10 #include "ash/resources/vector_icons/vector_icons.h"
11 #include "ash/root_window_controller.h"
12 #include "ash/shelf/shelf.h"
13 #include "ash/shell.h"
14 #include "ash/strings/grit/ash_strings.h"
15 #include "ash/style/ash_color_provider.h"
16 #include "ash/system/accessibility/dictation_button_tray.h"
17 #include "ash/system/accessibility/floating_menu_button.h"
18 #include "ash/system/accessibility/select_to_speak_tray.h"
19 #include "ash/system/tray/tray_constants.h"
20 #include "ash/system/virtual_keyboard/virtual_keyboard_tray.h"
21 #include "base/bind.h"
22 #include "ui/base/l10n/l10n_util.h"
23 #include "ui/views/controls/separator.h"
24 #include "ui/views/layout/box_layout.h"
25 #include "ui/views/metadata/metadata_impl_macros.h"
26 
27 namespace ash {
28 
29 namespace {
30 
31 // These constants are defined in DIP.
32 constexpr int kPanelPositionButtonPadding = 14;
33 constexpr int kPanelPositionButtonSize = 36;
34 constexpr int kSeparatorHeight = 16;
35 
36 // The view that hides itself if all of its children are not visible.
37 class DynamicRowView : public views::View {
38  public:
39   DynamicRowView() = default;
40 
41  protected:
42   // views::View:
ChildVisibilityChanged(views::View * child)43   void ChildVisibilityChanged(views::View* child) override {
44     bool any_visible = false;
45     for (auto* view : children()) {
46       any_visible |= view->GetVisible();
47     }
48     SetVisible(any_visible);
49   }
50 };
51 
CreateSeparator()52 std::unique_ptr<views::Separator> CreateSeparator() {
53   auto separator = std::make_unique<views::Separator>();
54   separator->SetColor(AshColorProvider::Get()->GetContentLayerColor(
55       AshColorProvider::ContentLayerType::kSeparatorColor));
56   separator->SetPreferredHeight(kSeparatorHeight);
57   int total_height = kUnifiedTopShortcutSpacing * 2 + kTrayItemSize;
58   int separator_spacing = (total_height - kSeparatorHeight) / 2;
59   separator->SetBorder(views::CreateEmptyBorder(
60       separator_spacing - kUnifiedTopShortcutSpacing, 0, separator_spacing, 0));
61   return separator;
62 }
63 
CreateButtonRowContainer(int padding)64 std::unique_ptr<views::View> CreateButtonRowContainer(int padding) {
65   auto button_container = std::make_unique<DynamicRowView>();
66   button_container->SetLayoutManager(std::make_unique<views::BoxLayout>(
67       views::BoxLayout::Orientation::kHorizontal,
68       gfx::Insets(0, padding, padding, padding), padding));
69   return button_container;
70 }
71 
GetDescriptionForMovedToPosition(FloatingMenuPosition position)72 std::string GetDescriptionForMovedToPosition(FloatingMenuPosition position) {
73   switch (position) {
74     case FloatingMenuPosition::kBottomRight:
75       return l10n_util::GetStringUTF8(
76           IDS_ASH_FLOATING_ACCESSIBILITY_MAIN_MENU_MOVED_BOTTOM_RIGHT);
77     case FloatingMenuPosition::kBottomLeft:
78       return l10n_util::GetStringUTF8(
79           IDS_ASH_FLOATING_ACCESSIBILITY_MAIN_MENU_MOVED_BOTTOM_LEFT);
80     case FloatingMenuPosition::kTopLeft:
81       return l10n_util::GetStringUTF8(
82           IDS_ASH_FLOATING_ACCESSIBILITY_MAIN_MENU_MOVED_TOP_LEFT);
83     case FloatingMenuPosition::kTopRight:
84       return l10n_util::GetStringUTF8(
85           IDS_ASH_FLOATING_ACCESSIBILITY_MAIN_MENU_MOVED_TOP_RIGHT);
86     case FloatingMenuPosition::kSystemDefault:
87       NOTREACHED();
88       return std::string();
89   }
90 }
91 
92 }  // namespace
93 
FloatingAccessibilityBubbleView(const TrayBubbleView::InitParams & init_params)94 FloatingAccessibilityBubbleView::FloatingAccessibilityBubbleView(
95     const TrayBubbleView::InitParams& init_params)
96     : TrayBubbleView(init_params) {
97   // Intercept ESC keypresses.
98   AddAccelerator(ui::Accelerator(ui::VKEY_ESCAPE, ui::EF_NONE));
99 }
100 
101 FloatingAccessibilityBubbleView::~FloatingAccessibilityBubbleView() = default;
102 
IsAnchoredToStatusArea() const103 bool FloatingAccessibilityBubbleView::IsAnchoredToStatusArea() const {
104   return false;
105 }
106 
AcceleratorPressed(const ui::Accelerator & accelerator)107 bool FloatingAccessibilityBubbleView::AcceleratorPressed(
108     const ui::Accelerator& accelerator) {
109   DCHECK_EQ(accelerator.key_code(), ui::VKEY_ESCAPE);
110   GetWidget()->Deactivate();
111   return true;
112 }
113 
BEGIN_METADATA(FloatingAccessibilityBubbleView,TrayBubbleView)114 BEGIN_METADATA(FloatingAccessibilityBubbleView, TrayBubbleView)
115 END_METADATA
116 
117 FloatingAccessibilityView::FloatingAccessibilityView(Delegate* delegate)
118     : delegate_(delegate) {
119 
120   Shelf* shelf = RootWindowController::ForTargetRootWindow()->shelf();
121   std::unique_ptr<views::View> feature_buttons_container =
122       CreateButtonRowContainer(kPanelPositionButtonPadding);
123   dictation_button_ = feature_buttons_container->AddChildView(
124       std::make_unique<DictationButtonTray>(shelf));
125   select_to_speak_button_ = feature_buttons_container->AddChildView(
126       std::make_unique<SelectToSpeakTray>(shelf));
127   virtual_keyboard_button_ = feature_buttons_container->AddChildView(
128       std::make_unique<VirtualKeyboardTray>(shelf));
129 
130   // It will be visible again as soon as any of the children becomes visible.
131   feature_buttons_container->SetVisible(false);
132 
133   std::unique_ptr<views::View> tray_button_container =
134       CreateButtonRowContainer(kUnifiedTopShortcutSpacing);
135   a11y_tray_button_ =
136       tray_button_container->AddChildView(std::make_unique<FloatingMenuButton>(
137           base::BindRepeating(
138               &FloatingAccessibilityView::OnA11yTrayButtonPressed,
139               base::Unretained(this)),
140           kUnifiedMenuAccessibilityIcon,
141           IDS_ASH_FLOATING_ACCESSIBILITY_DETAILED_MENU_OPEN,
142           /*flip_for_rtl*/ true));
143 
144   std::unique_ptr<views::View> position_button_container =
145       CreateButtonRowContainer(kPanelPositionButtonPadding);
146   position_button_ = position_button_container->AddChildView(
147       std::make_unique<FloatingMenuButton>(
148           base::BindRepeating(
149               &FloatingAccessibilityView::OnPositionButtonPressed,
150               base::Unretained(this)),
151           kAutoclickPositionBottomLeftIcon,
152           IDS_ASH_AUTOCLICK_OPTION_CHANGE_POSITION, /*flip_for_rtl*/ false,
153           kPanelPositionButtonSize, false, /* is_a11y_togglable */ false));
154 
155   AddChildView(std::move(feature_buttons_container));
156   AddChildView(std::move(tray_button_container));
157   AddChildView(CreateSeparator());
158   AddChildView(std::move(position_button_container));
159 
160   // Set view IDs for testing.
161   position_button_->SetID(static_cast<int>(ButtonId::kPosition));
162   a11y_tray_button_->SetID(static_cast<int>(ButtonId::kSettingsList));
163   dictation_button_->SetID(static_cast<int>(ButtonId::kDictation));
164   select_to_speak_button_->SetID(static_cast<int>(ButtonId::kSelectToSpeak));
165   virtual_keyboard_button_->SetID(static_cast<int>(ButtonId::kVirtualKeyboard));
166 }
167 
~FloatingAccessibilityView()168 FloatingAccessibilityView::~FloatingAccessibilityView() {}
169 
Initialize()170 void FloatingAccessibilityView::Initialize() {
171   for (auto* feature_view :
172        {dictation_button_, select_to_speak_button_, virtual_keyboard_button_}) {
173     feature_view->Initialize();
174     feature_view->CalculateTargetBounds();
175     feature_view->UpdateLayout();
176     feature_view->AddObserver(this);
177   }
178 }
179 
SetMenuPosition(FloatingMenuPosition position)180 void FloatingAccessibilityView::SetMenuPosition(FloatingMenuPosition position) {
181   switch (position) {
182     case FloatingMenuPosition::kBottomRight:
183       position_button_->SetVectorIcon(kAutoclickPositionBottomRightIcon);
184       return;
185     case FloatingMenuPosition::kBottomLeft:
186       position_button_->SetVectorIcon(kAutoclickPositionBottomLeftIcon);
187       return;
188     case FloatingMenuPosition::kTopLeft:
189       position_button_->SetVectorIcon(kAutoclickPositionTopLeftIcon);
190       return;
191     case FloatingMenuPosition::kTopRight:
192       position_button_->SetVectorIcon(kAutoclickPositionTopRightIcon);
193       return;
194     case FloatingMenuPosition::kSystemDefault:
195       position_button_->SetVectorIcon(base::i18n::IsRTL()
196                                           ? kAutoclickPositionBottomLeftIcon
197                                           : kAutoclickPositionBottomRightIcon);
198       return;
199   }
200 }
201 
SetDetailedViewShown(bool shown)202 void FloatingAccessibilityView::SetDetailedViewShown(bool shown) {
203   a11y_tray_button_->SetToggled(shown);
204 }
205 
FocusOnDetailedViewButton()206 void FloatingAccessibilityView::FocusOnDetailedViewButton() {
207   a11y_tray_button_->RequestFocus();
208 }
209 
OnA11yTrayButtonPressed()210 void FloatingAccessibilityView::OnA11yTrayButtonPressed() {
211   delegate_->OnDetailedMenuEnabled(!a11y_tray_button_->GetToggled());
212 }
213 
OnPositionButtonPressed()214 void FloatingAccessibilityView::OnPositionButtonPressed() {
215   FloatingMenuPosition new_position;
216   // Rotate clockwise throughout the screen positions.
217   switch (Shell::Get()->accessibility_controller()->GetFloatingMenuPosition()) {
218     case FloatingMenuPosition::kBottomRight:
219       new_position = FloatingMenuPosition::kBottomLeft;
220       break;
221     case FloatingMenuPosition::kBottomLeft:
222       new_position = FloatingMenuPosition::kTopLeft;
223       break;
224     case FloatingMenuPosition::kTopLeft:
225       new_position = FloatingMenuPosition::kTopRight;
226       break;
227     case FloatingMenuPosition::kTopRight:
228       new_position = FloatingMenuPosition::kBottomRight;
229       break;
230     case FloatingMenuPosition::kSystemDefault:
231       new_position = base::i18n::IsRTL() ? FloatingMenuPosition::kTopLeft
232                                          : FloatingMenuPosition::kBottomLeft;
233       break;
234   }
235   Shell::Get()->accessibility_controller()->SetFloatingMenuPosition(
236       new_position);
237   Shell::Get()
238       ->accessibility_controller()
239       ->TriggerAccessibilityAlertWithMessage(
240           GetDescriptionForMovedToPosition(new_position));
241 }
242 
OnViewVisibilityChanged(views::View * observed_view,views::View * starting_view)243 void FloatingAccessibilityView::OnViewVisibilityChanged(
244     views::View* observed_view,
245     views::View* starting_view) {
246   if (observed_view != starting_view)
247     return;
248   delegate_->OnLayoutChanged();
249 }
250 
251 BEGIN_METADATA(FloatingAccessibilityView, views::BoxLayoutView)
252 END_METADATA
253 
254 }  // namespace ash
255