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_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