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