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.
5 #include "ash/system/tray_accessibility.h"
7 #include "ash/accessibility_delegate.h"
8 #include "ash/metrics/user_metrics_recorder.h"
9 #include "ash/session/session_state_delegate.h"
10 #include "ash/shell.h"
11 #include "ash/system/tray/hover_highlight_view.h"
12 #include "ash/system/tray/system_tray.h"
13 #include "ash/system/tray/system_tray_delegate.h"
14 #include "ash/system/tray/system_tray_notifier.h"
15 #include "ash/system/tray/tray_constants.h"
16 #include "ash/system/tray/tray_details_view.h"
17 #include "ash/system/tray/tray_item_more.h"
18 #include "ash/system/tray/tray_popup_label_button.h"
19 #include "base/strings/utf_string_conversions.h"
20 #include "grit/ash_resources.h"
21 #include "grit/ash_strings.h"
22 #include "ui/base/l10n/l10n_util.h"
23 #include "ui/base/resource/resource_bundle.h"
24 #include "ui/gfx/image/image.h"
25 #include "ui/views/controls/image_view.h"
26 #include "ui/views/controls/label.h"
27 #include "ui/views/layout/box_layout.h"
28 #include "ui/views/widget/widget.h"
33 enum AccessibilityState
{
35 A11Y_SPOKEN_FEEDBACK
= 1 << 0,
36 A11Y_HIGH_CONTRAST
= 1 << 1,
37 A11Y_SCREEN_MAGNIFIER
= 1 << 2,
38 A11Y_LARGE_CURSOR
= 1 << 3,
39 A11Y_AUTOCLICK
= 1 << 4,
40 A11Y_VIRTUAL_KEYBOARD
= 1 << 5,
41 A11Y_BRAILLE_DISPLAY_CONNECTED
= 1 << 6,
44 uint32
GetAccessibilityState() {
45 AccessibilityDelegate
* delegate
=
46 Shell::GetInstance()->accessibility_delegate();
47 uint32 state
= A11Y_NONE
;
48 if (delegate
->IsSpokenFeedbackEnabled())
49 state
|= A11Y_SPOKEN_FEEDBACK
;
50 if (delegate
->IsHighContrastEnabled())
51 state
|= A11Y_HIGH_CONTRAST
;
52 if (delegate
->IsMagnifierEnabled())
53 state
|= A11Y_SCREEN_MAGNIFIER
;
54 if (delegate
->IsLargeCursorEnabled())
55 state
|= A11Y_LARGE_CURSOR
;
56 if (delegate
->IsAutoclickEnabled())
57 state
|= A11Y_AUTOCLICK
;
58 if (delegate
->IsVirtualKeyboardEnabled())
59 state
|= A11Y_VIRTUAL_KEYBOARD
;
60 if (delegate
->IsBrailleDisplayConnected())
61 state
|= A11Y_BRAILLE_DISPLAY_CONNECTED
;
65 user::LoginStatus
GetCurrentLoginStatus() {
66 return Shell::GetInstance()->system_tray_delegate()->GetUserLoginStatus();
73 class DefaultAccessibilityView
: public TrayItemMore
{
75 explicit DefaultAccessibilityView(SystemTrayItem
* owner
)
76 : TrayItemMore(owner
, true) {
77 ui::ResourceBundle
& bundle
= ui::ResourceBundle::GetSharedInstance();
78 SetImage(bundle
.GetImageNamed(IDR_AURA_UBER_TRAY_ACCESSIBILITY_DARK
).
80 base::string16 label
= bundle
.GetLocalizedString(
81 IDS_ASH_STATUS_TRAY_ACCESSIBILITY
);
83 SetAccessibleName(label
);
84 set_id(test::kAccessibilityTrayItemViewId
);
87 virtual ~DefaultAccessibilityView() {
91 DISALLOW_COPY_AND_ASSIGN(DefaultAccessibilityView
);
94 ////////////////////////////////////////////////////////////////////////////////
95 // ash::tray::AccessibilityPopupView
97 AccessibilityPopupView::AccessibilityPopupView(SystemTrayItem
* owner
,
98 uint32 enabled_state_bits
)
99 : TrayNotificationView(owner
, IDR_AURA_UBER_TRAY_ACCESSIBILITY_DARK
),
100 label_(CreateLabel(enabled_state_bits
)) {
104 views::Label
* AccessibilityPopupView::CreateLabel(uint32 enabled_state_bits
) {
105 DCHECK((enabled_state_bits
&
106 (A11Y_SPOKEN_FEEDBACK
| A11Y_BRAILLE_DISPLAY_CONNECTED
)) != 0);
108 if (enabled_state_bits
& A11Y_BRAILLE_DISPLAY_CONNECTED
) {
109 text
.append(l10n_util::GetStringUTF16(
110 IDS_ASH_STATUS_TRAY_BRAILLE_DISPLAY_CONNECTED_BUBBLE
));
112 if (enabled_state_bits
& A11Y_SPOKEN_FEEDBACK
) {
114 text
.append(base::ASCIIToUTF16(" "));
115 text
.append(l10n_util::GetStringUTF16(
116 IDS_ASH_STATUS_TRAY_SPOKEN_FEEDBACK_ENABLED_BUBBLE
));
118 views::Label
* label
= new views::Label(text
);
119 label
->SetMultiLine(true);
120 label
->SetHorizontalAlignment(gfx::ALIGN_LEFT
);
124 ////////////////////////////////////////////////////////////////////////////////
125 // ash::tray::AccessibilityDetailedView
127 AccessibilityDetailedView::AccessibilityDetailedView(
128 SystemTrayItem
* owner
, user::LoginStatus login
) :
129 TrayDetailsView(owner
),
130 spoken_feedback_view_(NULL
),
131 high_contrast_view_(NULL
),
132 screen_magnifier_view_(NULL
),
133 large_cursor_view_(NULL
),
135 settings_view_(NULL
),
136 autoclick_view_(NULL
),
137 virtual_keyboard_view_(NULL
),
138 spoken_feedback_enabled_(false),
139 high_contrast_enabled_(false),
140 screen_magnifier_enabled_(false),
141 large_cursor_enabled_(false),
142 autoclick_enabled_(false),
143 virtual_keyboard_enabled_(false),
148 AppendAccessibilityList();
150 CreateSpecialRow(IDS_ASH_STATUS_TRAY_ACCESSIBILITY_TITLE
, this);
155 void AccessibilityDetailedView::AppendAccessibilityList() {
156 CreateScrollableList();
157 ui::ResourceBundle
& bundle
= ui::ResourceBundle::GetSharedInstance();
159 AccessibilityDelegate
* delegate
=
160 Shell::GetInstance()->accessibility_delegate();
161 spoken_feedback_enabled_
= delegate
->IsSpokenFeedbackEnabled();
162 spoken_feedback_view_
= AddScrollListItem(
163 bundle
.GetLocalizedString(
164 IDS_ASH_STATUS_TRAY_ACCESSIBILITY_SPOKEN_FEEDBACK
),
165 spoken_feedback_enabled_
? gfx::Font::BOLD
: gfx::Font::NORMAL
,
166 spoken_feedback_enabled_
);
168 // Large Cursor item is shown only in Login screen.
169 if (login_
== user::LOGGED_IN_NONE
) {
170 large_cursor_enabled_
= delegate
->IsLargeCursorEnabled();
171 large_cursor_view_
= AddScrollListItem(
172 bundle
.GetLocalizedString(
173 IDS_ASH_STATUS_TRAY_ACCESSIBILITY_LARGE_CURSOR
),
174 large_cursor_enabled_
? gfx::Font::BOLD
: gfx::Font::NORMAL
,
175 large_cursor_enabled_
);
178 high_contrast_enabled_
= delegate
->IsHighContrastEnabled();
179 high_contrast_view_
= AddScrollListItem(
180 bundle
.GetLocalizedString(
181 IDS_ASH_STATUS_TRAY_ACCESSIBILITY_HIGH_CONTRAST_MODE
),
182 high_contrast_enabled_
? gfx::Font::BOLD
: gfx::Font::NORMAL
,
183 high_contrast_enabled_
);
184 screen_magnifier_enabled_
= delegate
->IsMagnifierEnabled();
185 screen_magnifier_view_
= AddScrollListItem(
186 bundle
.GetLocalizedString(
187 IDS_ASH_STATUS_TRAY_ACCESSIBILITY_SCREEN_MAGNIFIER
),
188 screen_magnifier_enabled_
? gfx::Font::BOLD
: gfx::Font::NORMAL
,
189 screen_magnifier_enabled_
);
191 // Don't show autoclick option at login screen.
192 if (login_
!= user::LOGGED_IN_NONE
) {
193 autoclick_enabled_
= delegate
->IsAutoclickEnabled();
194 autoclick_view_
= AddScrollListItem(
195 bundle
.GetLocalizedString(
196 IDS_ASH_STATUS_TRAY_ACCESSIBILITY_AUTOCLICK
),
197 autoclick_enabled_
? gfx::Font::BOLD
: gfx::Font::NORMAL
,
201 virtual_keyboard_enabled_
= delegate
->IsVirtualKeyboardEnabled();
202 virtual_keyboard_view_
= AddScrollListItem(
203 bundle
.GetLocalizedString(
204 IDS_ASH_STATUS_TRAY_ACCESSIBILITY_VIRTUAL_KEYBOARD
),
205 virtual_keyboard_enabled_
? gfx::Font::BOLD
: gfx::Font::NORMAL
,
206 virtual_keyboard_enabled_
);
209 void AccessibilityDetailedView::AppendHelpEntries() {
210 // Currently the help page requires a browser window.
211 // TODO(yoshiki): show this even on login/lock screen. crbug.com/158286
212 bool userAddingRunning
= ash::Shell::GetInstance()
213 ->session_state_delegate()
214 ->IsInSecondaryLoginScreen();
216 if (login_
== user::LOGGED_IN_NONE
||
217 login_
== user::LOGGED_IN_LOCKED
|| userAddingRunning
)
220 views::View
* bottom_row
= new View();
221 views::BoxLayout
* layout
= new
222 views::BoxLayout(views::BoxLayout::kHorizontal
,
223 kTrayMenuBottomRowPadding
,
224 kTrayMenuBottomRowPadding
,
225 kTrayMenuBottomRowPaddingBetweenItems
);
226 layout
->SetDefaultFlex(1);
227 bottom_row
->SetLayoutManager(layout
);
229 ui::ResourceBundle
& bundle
= ui::ResourceBundle::GetSharedInstance();
231 TrayPopupLabelButton
* help
= new TrayPopupLabelButton(
233 bundle
.GetLocalizedString(
234 IDS_ASH_STATUS_TRAY_ACCESSIBILITY_LEARN_MORE
));
235 bottom_row
->AddChildView(help
);
238 TrayPopupLabelButton
* settings
= new TrayPopupLabelButton(
240 bundle
.GetLocalizedString(
241 IDS_ASH_STATUS_TRAY_ACCESSIBILITY_SETTINGS
));
242 bottom_row
->AddChildView(settings
);
243 settings_view_
= settings
;
245 AddChildView(bottom_row
);
248 HoverHighlightView
* AccessibilityDetailedView::AddScrollListItem(
249 const base::string16
& text
,
250 gfx::Font::FontStyle style
,
252 HoverHighlightView
* container
= new HoverHighlightView(this);
253 container
->AddCheckableLabel(text
, style
, checked
);
254 scroll_content()->AddChildView(container
);
258 void AccessibilityDetailedView::OnViewClicked(views::View
* sender
) {
259 AccessibilityDelegate
* delegate
=
260 Shell::GetInstance()->accessibility_delegate();
261 if (sender
== footer()->content()) {
262 TransitionToDefaultView();
263 } else if (sender
== spoken_feedback_view_
) {
264 Shell::GetInstance()->metrics()->RecordUserMetricsAction(
265 delegate
->IsSpokenFeedbackEnabled() ?
266 ash::UMA_STATUS_AREA_DISABLE_SPOKEN_FEEDBACK
:
267 ash::UMA_STATUS_AREA_ENABLE_SPOKEN_FEEDBACK
);
268 delegate
->ToggleSpokenFeedback(ash::A11Y_NOTIFICATION_NONE
);
269 } else if (sender
== high_contrast_view_
) {
270 Shell::GetInstance()->metrics()->RecordUserMetricsAction(
271 delegate
->IsHighContrastEnabled() ?
272 ash::UMA_STATUS_AREA_DISABLE_HIGH_CONTRAST
:
273 ash::UMA_STATUS_AREA_ENABLE_HIGH_CONTRAST
);
274 delegate
->ToggleHighContrast();
275 } else if (sender
== screen_magnifier_view_
) {
276 Shell::GetInstance()->metrics()->RecordUserMetricsAction(
277 delegate
->IsMagnifierEnabled() ?
278 ash::UMA_STATUS_AREA_DISABLE_MAGNIFIER
:
279 ash::UMA_STATUS_AREA_ENABLE_MAGNIFIER
);
280 delegate
->SetMagnifierEnabled(!delegate
->IsMagnifierEnabled());
281 } else if (large_cursor_view_
&& sender
== large_cursor_view_
) {
282 Shell::GetInstance()->metrics()->RecordUserMetricsAction(
283 delegate
->IsLargeCursorEnabled() ?
284 ash::UMA_STATUS_AREA_DISABLE_LARGE_CURSOR
:
285 ash::UMA_STATUS_AREA_ENABLE_LARGE_CURSOR
);
286 delegate
->SetLargeCursorEnabled(!delegate
->IsLargeCursorEnabled());
287 } else if (autoclick_view_
&& sender
== autoclick_view_
) {
288 Shell::GetInstance()->metrics()->RecordUserMetricsAction(
289 delegate
->IsAutoclickEnabled() ?
290 ash::UMA_STATUS_AREA_DISABLE_AUTO_CLICK
:
291 ash::UMA_STATUS_AREA_ENABLE_AUTO_CLICK
);
292 delegate
->SetAutoclickEnabled(!delegate
->IsAutoclickEnabled());
293 } else if (virtual_keyboard_view_
&& sender
== virtual_keyboard_view_
) {
294 Shell::GetInstance()->metrics()->RecordUserMetricsAction(
295 delegate
->IsVirtualKeyboardEnabled() ?
296 ash::UMA_STATUS_AREA_DISABLE_VIRTUAL_KEYBOARD
:
297 ash::UMA_STATUS_AREA_ENABLE_VIRTUAL_KEYBOARD
);
298 delegate
->SetVirtualKeyboardEnabled(!delegate
->IsVirtualKeyboardEnabled());
302 void AccessibilityDetailedView::ButtonPressed(views::Button
* sender
,
303 const ui::Event
& event
) {
304 SystemTrayDelegate
* tray_delegate
=
305 Shell::GetInstance()->system_tray_delegate();
306 if (sender
== help_view_
)
307 tray_delegate
->ShowAccessibilityHelp();
308 else if (sender
== settings_view_
)
309 tray_delegate
->ShowAccessibilitySettings();
314 ////////////////////////////////////////////////////////////////////////////////
315 // ash::TrayAccessibility
317 TrayAccessibility::TrayAccessibility(SystemTray
* system_tray
)
318 : TrayImageItem(system_tray
, IDR_AURA_UBER_TRAY_ACCESSIBILITY
),
320 detailed_popup_(NULL
),
321 detailed_menu_(NULL
),
322 request_popup_view_state_(A11Y_NONE
),
323 tray_icon_visible_(false),
324 login_(GetCurrentLoginStatus()),
325 previous_accessibility_state_(GetAccessibilityState()),
326 show_a11y_menu_on_lock_screen_(true) {
327 DCHECK(Shell::GetInstance()->delegate());
329 Shell::GetInstance()->system_tray_notifier()->AddAccessibilityObserver(this);
332 TrayAccessibility::~TrayAccessibility() {
333 Shell::GetInstance()->system_tray_notifier()->
334 RemoveAccessibilityObserver(this);
337 void TrayAccessibility::SetTrayIconVisible(bool visible
) {
339 tray_view()->SetVisible(visible
);
340 tray_icon_visible_
= visible
;
343 tray::AccessibilityDetailedView
* TrayAccessibility::CreateDetailedMenu() {
344 return new tray::AccessibilityDetailedView(this, login_
);
347 bool TrayAccessibility::GetInitialVisibility() {
348 // Shows accessibility icon if any accessibility feature is enabled.
349 // Otherwise, doen't show it.
350 return GetAccessibilityState() != A11Y_NONE
;
353 views::View
* TrayAccessibility::CreateDefaultView(user::LoginStatus status
) {
354 CHECK(default_
== NULL
);
356 // Shows accessibility menu if:
357 // - on login screen (not logged in);
358 // - "Enable accessibility menu" on chrome://settings is checked;
359 // - or any of accessibility features is enabled
360 // Otherwise, not shows it.
361 AccessibilityDelegate
* delegate
=
362 Shell::GetInstance()->accessibility_delegate();
363 if (login_
!= user::LOGGED_IN_NONE
&&
364 !delegate
->ShouldShowAccessibilityMenu() &&
365 // On login screen, keeps the initial visibility of the menu.
366 (status
!= user::LOGGED_IN_LOCKED
|| !show_a11y_menu_on_lock_screen_
))
369 CHECK(default_
== NULL
);
370 default_
= new tray::DefaultAccessibilityView(this);
375 views::View
* TrayAccessibility::CreateDetailedView(user::LoginStatus status
) {
376 CHECK(detailed_popup_
== NULL
);
377 CHECK(detailed_menu_
== NULL
);
379 if (request_popup_view_state_
) {
381 new tray::AccessibilityPopupView(this, request_popup_view_state_
);
382 request_popup_view_state_
= A11Y_NONE
;
383 return detailed_popup_
;
385 Shell::GetInstance()->metrics()->RecordUserMetricsAction(
386 ash::UMA_STATUS_AREA_DETAILED_ACCESSABILITY
);
387 detailed_menu_
= CreateDetailedMenu();
388 return detailed_menu_
;
392 void TrayAccessibility::DestroyDefaultView() {
396 void TrayAccessibility::DestroyDetailedView() {
397 detailed_popup_
= NULL
;
398 detailed_menu_
= NULL
;
401 void TrayAccessibility::UpdateAfterLoginStatusChange(user::LoginStatus status
) {
402 // Stores the a11y feature status on just entering the lock screen.
403 if (login_
!= user::LOGGED_IN_LOCKED
&& status
== user::LOGGED_IN_LOCKED
)
404 show_a11y_menu_on_lock_screen_
= (GetAccessibilityState() != A11Y_NONE
);
407 SetTrayIconVisible(GetInitialVisibility());
410 void TrayAccessibility::OnAccessibilityModeChanged(
411 AccessibilityNotificationVisibility notify
) {
412 SetTrayIconVisible(GetInitialVisibility());
414 uint32 accessibility_state
= GetAccessibilityState();
415 // We'll get an extra notification if a braille display is connected when
416 // spoken feedback wasn't already enabled. This is because the braille
417 // connection state is already updated when spoken feedback is enabled so
418 // that the notifications can be consolidated into one. Therefore, we
419 // return early if there's no change in the state that we keep track of.
420 if (accessibility_state
== previous_accessibility_state_
)
422 // Contains bits for spoken feedback and braille display connected currently
424 uint32 being_enabled
=
425 (accessibility_state
& ~previous_accessibility_state_
) &
426 (A11Y_SPOKEN_FEEDBACK
| A11Y_BRAILLE_DISPLAY_CONNECTED
);
427 if ((notify
== ash::A11Y_NOTIFICATION_SHOW
) && being_enabled
!= A11Y_NONE
) {
428 // Shows popup if |notify| is true and the spoken feedback is being enabled.
429 request_popup_view_state_
= being_enabled
;
430 PopupDetailedView(kTrayPopupAutoCloseDelayForTextInSeconds
, false);
433 detailed_popup_
->GetWidget()->Close();
435 detailed_menu_
->GetWidget()->Close();
438 previous_accessibility_state_
= accessibility_state
;