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