tray_accessibility.cc revision 5f1c94371a64b3196d4be9466099bb892df9b88e
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
29namespace ash {
30namespace {
31
32enum 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
43uint32 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
64user::LoginStatus GetCurrentLoginStatus() {
65  return Shell::GetInstance()->system_tray_delegate()->GetUserLoginStatus();
66}
67
68}  // namespace
69
70namespace tray {
71
72class DefaultAccessibilityView : public TrayItemMore {
73 public:
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
86  virtual ~DefaultAccessibilityView() {
87  }
88
89 private:
90  DISALLOW_COPY_AND_ASSIGN(DefaultAccessibilityView);
91};
92
93////////////////////////////////////////////////////////////////////////////////
94// ash::tray::AccessibilityPopupView
95
96AccessibilityPopupView::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
103views::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
126AccessibilityDetailedView::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
154void 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
208void 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->SetDefaultFlex(1);
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
243HoverHighlightView* 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
253void 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
297void 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
312TrayAccessibility::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
327TrayAccessibility::~TrayAccessibility() {
328  Shell::GetInstance()->system_tray_notifier()->
329      RemoveAccessibilityObserver(this);
330}
331
332void TrayAccessibility::SetTrayIconVisible(bool visible) {
333  if (tray_view())
334    tray_view()->SetVisible(visible);
335  tray_icon_visible_ = visible;
336}
337
338tray::AccessibilityDetailedView* TrayAccessibility::CreateDetailedMenu() {
339  return new tray::AccessibilityDetailedView(this, login_);
340}
341
342bool 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
348views::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
370views::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
387void TrayAccessibility::DestroyDefaultView() {
388  default_ = NULL;
389}
390
391void TrayAccessibility::DestroyDetailedView() {
392  detailed_popup_ = NULL;
393  detailed_menu_ = NULL;
394}
395
396void 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
405void 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