• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.
4 
5 #include "ash/system/tray_accessibility.h"
6 
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"
29 
30 namespace ash {
31 namespace {
32 
33 enum AccessibilityState {
34   A11Y_NONE = 0,
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,
42 };
43 
GetAccessibilityState()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;
62   return state;
63 }
64 
GetCurrentLoginStatus()65 user::LoginStatus GetCurrentLoginStatus() {
66   return Shell::GetInstance()->system_tray_delegate()->GetUserLoginStatus();
67 }
68 
69 }  // namespace
70 
71 namespace tray {
72 
73 class DefaultAccessibilityView : public TrayItemMore {
74  public:
DefaultAccessibilityView(SystemTrayItem * owner)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).
79                     ToImageSkia());
80     base::string16 label = bundle.GetLocalizedString(
81         IDS_ASH_STATUS_TRAY_ACCESSIBILITY);
82     SetLabel(label);
83     SetAccessibleName(label);
84     set_id(test::kAccessibilityTrayItemViewId);
85   }
86 
~DefaultAccessibilityView()87   virtual ~DefaultAccessibilityView() {
88   }
89 
90  private:
91   DISALLOW_COPY_AND_ASSIGN(DefaultAccessibilityView);
92 };
93 
94 ////////////////////////////////////////////////////////////////////////////////
95 // ash::tray::AccessibilityPopupView
96 
AccessibilityPopupView(SystemTrayItem * owner,uint32 enabled_state_bits)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)) {
101   InitView(label_);
102 }
103 
CreateLabel(uint32 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);
107   base::string16 text;
108   if (enabled_state_bits & A11Y_BRAILLE_DISPLAY_CONNECTED) {
109     text.append(l10n_util::GetStringUTF16(
110         IDS_ASH_STATUS_TRAY_BRAILLE_DISPLAY_CONNECTED_BUBBLE));
111   }
112   if (enabled_state_bits & A11Y_SPOKEN_FEEDBACK) {
113     if (!text.empty())
114       text.append(base::ASCIIToUTF16(" "));
115     text.append(l10n_util::GetStringUTF16(
116         IDS_ASH_STATUS_TRAY_SPOKEN_FEEDBACK_ENABLED_BUBBLE));
117   }
118   views::Label* label = new views::Label(text);
119   label->SetMultiLine(true);
120   label->SetHorizontalAlignment(gfx::ALIGN_LEFT);
121   return label;
122 }
123 
124 ////////////////////////////////////////////////////////////////////////////////
125 // ash::tray::AccessibilityDetailedView
126 
AccessibilityDetailedView(SystemTrayItem * owner,user::LoginStatus login)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),
134         help_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),
144         login_(login) {
145 
146   Reset();
147 
148   AppendAccessibilityList();
149   AppendHelpEntries();
150   CreateSpecialRow(IDS_ASH_STATUS_TRAY_ACCESSIBILITY_TITLE, this);
151 
152   Layout();
153 }
154 
AppendAccessibilityList()155 void AccessibilityDetailedView::AppendAccessibilityList() {
156   CreateScrollableList();
157   ui::ResourceBundle& bundle = ui::ResourceBundle::GetSharedInstance();
158 
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_);
167 
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_);
176   }
177 
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_);
190 
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,
198         autoclick_enabled_);
199   }
200 
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_);
207 }
208 
AppendHelpEntries()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();
215 
216   if (login_ == user::LOGGED_IN_NONE ||
217       login_ == user::LOGGED_IN_LOCKED || userAddingRunning)
218     return;
219 
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);
228 
229   ui::ResourceBundle& bundle = ui::ResourceBundle::GetSharedInstance();
230 
231   TrayPopupLabelButton* help = new TrayPopupLabelButton(
232       this,
233       bundle.GetLocalizedString(
234           IDS_ASH_STATUS_TRAY_ACCESSIBILITY_LEARN_MORE));
235   bottom_row->AddChildView(help);
236   help_view_ = help;
237 
238   TrayPopupLabelButton* settings = new TrayPopupLabelButton(
239       this,
240       bundle.GetLocalizedString(
241           IDS_ASH_STATUS_TRAY_ACCESSIBILITY_SETTINGS));
242   bottom_row->AddChildView(settings);
243   settings_view_ = settings;
244 
245   AddChildView(bottom_row);
246 }
247 
AddScrollListItem(const base::string16 & text,gfx::Font::FontStyle style,bool checked)248 HoverHighlightView* AccessibilityDetailedView::AddScrollListItem(
249     const base::string16& text,
250     gfx::Font::FontStyle style,
251     bool checked) {
252   HoverHighlightView* container = new HoverHighlightView(this);
253   container->AddCheckableLabel(text, style, checked);
254   scroll_content()->AddChildView(container);
255   return container;
256 }
257 
OnViewClicked(views::View * sender)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());
299   }
300 }
301 
ButtonPressed(views::Button * sender,const ui::Event & event)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();
310 }
311 
312 }  // namespace tray
313 
314 ////////////////////////////////////////////////////////////////////////////////
315 // ash::TrayAccessibility
316 
TrayAccessibility(SystemTray * system_tray)317 TrayAccessibility::TrayAccessibility(SystemTray* system_tray)
318     : TrayImageItem(system_tray, IDR_AURA_UBER_TRAY_ACCESSIBILITY),
319       default_(NULL),
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());
328   DCHECK(system_tray);
329   Shell::GetInstance()->system_tray_notifier()->AddAccessibilityObserver(this);
330 }
331 
~TrayAccessibility()332 TrayAccessibility::~TrayAccessibility() {
333   Shell::GetInstance()->system_tray_notifier()->
334       RemoveAccessibilityObserver(this);
335 }
336 
SetTrayIconVisible(bool visible)337 void TrayAccessibility::SetTrayIconVisible(bool visible) {
338   if (tray_view())
339     tray_view()->SetVisible(visible);
340   tray_icon_visible_ = visible;
341 }
342 
CreateDetailedMenu()343 tray::AccessibilityDetailedView* TrayAccessibility::CreateDetailedMenu() {
344   return new tray::AccessibilityDetailedView(this, login_);
345 }
346 
GetInitialVisibility()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;
351 }
352 
CreateDefaultView(user::LoginStatus status)353 views::View* TrayAccessibility::CreateDefaultView(user::LoginStatus status) {
354   CHECK(default_ == NULL);
355 
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_))
367     return NULL;
368 
369   CHECK(default_ == NULL);
370   default_ = new tray::DefaultAccessibilityView(this);
371 
372   return default_;
373 }
374 
CreateDetailedView(user::LoginStatus status)375 views::View* TrayAccessibility::CreateDetailedView(user::LoginStatus status) {
376   CHECK(detailed_popup_ == NULL);
377   CHECK(detailed_menu_ == NULL);
378 
379   if (request_popup_view_state_) {
380     detailed_popup_ =
381         new tray::AccessibilityPopupView(this, request_popup_view_state_);
382     request_popup_view_state_ = A11Y_NONE;
383     return detailed_popup_;
384   } else {
385     Shell::GetInstance()->metrics()->RecordUserMetricsAction(
386         ash::UMA_STATUS_AREA_DETAILED_ACCESSABILITY);
387     detailed_menu_ = CreateDetailedMenu();
388     return detailed_menu_;
389   }
390 }
391 
DestroyDefaultView()392 void TrayAccessibility::DestroyDefaultView() {
393   default_ = NULL;
394 }
395 
DestroyDetailedView()396 void TrayAccessibility::DestroyDetailedView() {
397   detailed_popup_ = NULL;
398   detailed_menu_ = NULL;
399 }
400 
UpdateAfterLoginStatusChange(user::LoginStatus status)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);
405 
406   login_ = status;
407   SetTrayIconVisible(GetInitialVisibility());
408 }
409 
OnAccessibilityModeChanged(AccessibilityNotificationVisibility notify)410 void TrayAccessibility::OnAccessibilityModeChanged(
411     AccessibilityNotificationVisibility notify) {
412   SetTrayIconVisible(GetInitialVisibility());
413 
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_)
421     return;
422   // Contains bits for spoken feedback and braille display connected currently
423   // being enabled.
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);
431   } else {
432     if (detailed_popup_)
433       detailed_popup_->GetWidget()->Close();
434     if (detailed_menu_)
435       detailed_menu_->GetWidget()->Close();
436   }
437 
438   previous_accessibility_state_ = accessibility_state;
439 }
440 
441 }  // namespace ash
442