Home | History | Annotate | Download | only in system
      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