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 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 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: 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 87 virtual ~DefaultAccessibilityView() { 88 } 89 90 private: 91 DISALLOW_COPY_AND_ASSIGN(DefaultAccessibilityView); 92 }; 93 94 //////////////////////////////////////////////////////////////////////////////// 95 // ash::tray::AccessibilityPopupView 96 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 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 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 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 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 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 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 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 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 332 TrayAccessibility::~TrayAccessibility() { 333 Shell::GetInstance()->system_tray_notifier()-> 334 RemoveAccessibilityObserver(this); 335 } 336 337 void TrayAccessibility::SetTrayIconVisible(bool visible) { 338 if (tray_view()) 339 tray_view()->SetVisible(visible); 340 tray_icon_visible_ = visible; 341 } 342 343 tray::AccessibilityDetailedView* TrayAccessibility::CreateDetailedMenu() { 344 return new tray::AccessibilityDetailedView(this, login_); 345 } 346 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 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 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 392 void TrayAccessibility::DestroyDefaultView() { 393 default_ = NULL; 394 } 395 396 void TrayAccessibility::DestroyDetailedView() { 397 detailed_popup_ = NULL; 398 detailed_menu_ = NULL; 399 } 400 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 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