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