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/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 
     29 namespace ash {
     30 namespace {
     31 
     32 enum 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 
     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   if (delegate->IsVirtualKeyboardEnabled())
     58     state |= A11Y_VIRTUAL_KEYBOARD;
     59   if (delegate->IsBrailleDisplayConnected())
     60     state |= A11Y_BRAILLE_DISPLAY_CONNECTED;
     61   return state;
     62 }
     63 
     64 user::LoginStatus GetCurrentLoginStatus() {
     65   return Shell::GetInstance()->system_tray_delegate()->GetUserLoginStatus();
     66 }
     67 
     68 }  // namespace
     69 
     70 namespace tray {
     71 
     72 class 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 
     96 AccessibilityPopupView::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 
    103 views::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 
    126 AccessibilityDetailedView::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 
    154 void 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 
    208 void 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->set_main_axis_alignment(views::BoxLayout::MAIN_AXIS_ALIGNMENT_FILL);
    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 
    243 HoverHighlightView* 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 
    253 void 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 
    297 void 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 
    312 TrayAccessibility::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 
    327 TrayAccessibility::~TrayAccessibility() {
    328   Shell::GetInstance()->system_tray_notifier()->
    329       RemoveAccessibilityObserver(this);
    330 }
    331 
    332 void TrayAccessibility::SetTrayIconVisible(bool visible) {
    333   if (tray_view())
    334     tray_view()->SetVisible(visible);
    335   tray_icon_visible_ = visible;
    336 }
    337 
    338 tray::AccessibilityDetailedView* TrayAccessibility::CreateDetailedMenu() {
    339   return new tray::AccessibilityDetailedView(this, login_);
    340 }
    341 
    342 bool 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 
    348 views::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 
    370 views::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 
    387 void TrayAccessibility::DestroyDefaultView() {
    388   default_ = NULL;
    389 }
    390 
    391 void TrayAccessibility::DestroyDetailedView() {
    392   detailed_popup_ = NULL;
    393   detailed_menu_ = NULL;
    394 }
    395 
    396 void 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 
    405 void 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