Commit 8a96963e authored by Anatoliy Potapchuk's avatar Anatoliy Potapchuk Committed by Commit Bot

[Ash] Add framework for native a11y in kiosk mode

This cl adds a new policy, which controls the visilibity of the
FloatingAccessibilityView via FloatingAccessibilityController.

Bug: 1061068
Change-Id: Ie0b5cbe18f714cc6b848fb8f71b92bc120a2f19a
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2134826
Commit-Queue: Anatoliy Potapchuk <apotapchuk@chromium.org>
Reviewed-by: default avatarXiyuan Xia <xiyuan@chromium.org>
Reviewed-by: default avatarDominic Mazzoni <dmazzoni@chromium.org>
Reviewed-by: default avatarAnqing Zhao <anqing@google.com>
Cr-Commit-Position: refs/heads/master@{#758815}
parent 648c66cc
......@@ -734,6 +734,10 @@ component("ash") {
"system/accessibility/autoclick_scroll_view.h",
"system/accessibility/dictation_button_tray.cc",
"system/accessibility/dictation_button_tray.h",
"system/accessibility/floating_accessibility_controller.cc",
"system/accessibility/floating_accessibility_controller.h",
"system/accessibility/floating_accessibility_view.cc",
"system/accessibility/floating_accessibility_view.h",
"system/accessibility/floating_menu_button.cc",
"system/accessibility/floating_menu_button.h",
"system/accessibility/select_to_speak_tray.cc",
......@@ -1961,6 +1965,7 @@ test("ash_unittests") {
"system/accessibility/accessibility_feature_pod_controller_unittest.cc",
"system/accessibility/autoclick_menu_bubble_controller_unittest.cc",
"system/accessibility/dictation_button_tray_unittest.cc",
"system/accessibility/floating_accessibility_controller_unittest.cc",
"system/accessibility/select_to_speak_tray_unittest.cc",
"system/accessibility/switch_access_menu_bubble_controller_unittest.cc",
"system/accessibility/tray_accessibility_unittest.cc",
......
......@@ -32,6 +32,7 @@
#include "ash/sticky_keys/sticky_keys_controller.h"
#include "ash/strings/grit/ash_strings.h"
#include "ash/system/accessibility/accessibility_feature_disable_dialog.h"
#include "ash/system/accessibility/floating_accessibility_controller.h"
#include "ash/system/accessibility/switch_access_menu_bubble_controller.h"
#include "ash/system/power/backlights_forced_off_setter.h"
#include "ash/system/power/power_status.h"
......@@ -590,6 +591,10 @@ void AccessibilityControllerImpl::RegisterProfilePrefs(
registry->RegisterBooleanPref(
prefs::kAccessibilityFloatingMenuEnabled, false,
user_prefs::PrefRegistrySyncable::SYNCABLE_OS_PREF);
registry->RegisterIntegerPref(
prefs::kAccessibilityFloatingMenuPosition,
static_cast<int>(kDefaultFloatingMenuPosition),
user_prefs::PrefRegistrySyncable::SYNCABLE_OS_PREF);
registry->RegisterBooleanPref(
prefs::kAccessibilityFocusHighlightEnabled, false,
user_prefs::PrefRegistrySyncable::SYNCABLE_OS_PREF);
......@@ -1147,6 +1152,20 @@ bool AccessibilityControllerImpl::IsEnterpriseIconVisibleForVirtualKeyboard() {
return virtual_keyboard().IsEnterpriseIconVisible();
}
void AccessibilityControllerImpl::ShowFloatingMenuIfEnabled() {
if (floating_menu().enabled()) {
DCHECK(!floating_menu_controller_);
floating_menu_controller_ =
std::make_unique<FloatingAccessibilityController>();
floating_menu_controller_->Show(GetFloatingMenuPosition());
}
}
FloatingAccessibilityController*
AccessibilityControllerImpl::GetFloatingMenuControllerForTesting() {
return floating_menu_controller_.get();
}
void AccessibilityControllerImpl::SetTabletModeShelfNavigationButtonsEnabled(
bool enabled) {
if (!active_user_prefs_)
......@@ -1387,6 +1406,11 @@ void AccessibilityControllerImpl::ObservePrefs(PrefService* prefs) {
base::BindRepeating(
&AccessibilityControllerImpl::UpdateAutoclickMenuPositionFromPref,
base::Unretained(this)));
pref_change_registrar_->Add(
prefs::kAccessibilityFloatingMenuPosition,
base::BindRepeating(
&AccessibilityControllerImpl::UpdateFloatingMenuPositionFromPref,
base::Unretained(this)));
pref_change_registrar_->Add(
prefs::kAccessibilityLargeCursorDipSize,
base::BindRepeating(
......@@ -1445,6 +1469,7 @@ void AccessibilityControllerImpl::ObservePrefs(PrefService* prefs) {
UpdateAutoclickStabilizePositionFromPref();
UpdateAutoclickMovementThresholdFromPref();
UpdateAutoclickMenuPositionFromPref();
UpdateFloatingMenuPositionFromPref();
UpdateLargeCursorFromPref();
UpdateShortcutsEnabledFromPref();
UpdateTabletModeShelfNavigationButtonsFromPref();
......@@ -1547,6 +1572,26 @@ void AccessibilityControllerImpl::OnAutoclickScrollableBoundsFound(
bounds_in_screen);
}
void AccessibilityControllerImpl::SetFloatingMenuPosition(
FloatingMenuPosition position) {
if (!active_user_prefs_)
return;
active_user_prefs_->SetInteger(prefs::kAccessibilityFloatingMenuPosition,
static_cast<int>(position));
active_user_prefs_->CommitPendingWrite();
}
void AccessibilityControllerImpl::UpdateFloatingMenuPositionFromPref() {
if (floating_menu_controller_)
floating_menu_controller_->SetMenuPosition(GetFloatingMenuPosition());
}
FloatingMenuPosition AccessibilityControllerImpl::GetFloatingMenuPosition() {
DCHECK(active_user_prefs_);
return static_cast<FloatingMenuPosition>(active_user_prefs_->GetInteger(
prefs::kAccessibilityFloatingMenuPosition));
}
void AccessibilityControllerImpl::UpdateLargeCursorFromPref() {
DCHECK(active_user_prefs_);
const bool enabled =
......
......@@ -36,6 +36,7 @@ namespace ash {
class AccessibilityHighlightController;
class AccessibilityObserver;
class FloatingAccessibilityController;
class ScopedBacklightsForcedOff;
class SelectToSpeakEventHandler;
class SwitchAccessMenuBubbleController;
......@@ -58,8 +59,8 @@ class ASH_EXPORT AccessibilityControllerImpl : public AccessibilityController,
kCaretHighlight,
KCursorHighlight,
kDictation,
kFocusHighlight,
kFloatingMenu,
kFocusHighlight,
kFullscreenMagnifier,
kDockedMagnifier,
kHighContrast,
......@@ -167,9 +168,9 @@ class ASH_EXPORT AccessibilityControllerImpl : public AccessibilityController,
Feature& caret_highlight() const;
Feature& cursor_highlight() const;
FeatureWithDialog& dictation() const;
Feature& floating_menu() const;
Feature& focus_highlight() const;
FeatureWithDialog& fullscreen_magnifier() const;
Feature& floating_menu() const;
FeatureWithDialog& docked_magnifier() const;
FeatureWithDialog& high_contrast() const;
Feature& large_cursor() const;
......@@ -205,6 +206,10 @@ class ASH_EXPORT AccessibilityControllerImpl : public AccessibilityController,
FloatingMenuPosition GetAutoclickMenuPosition();
void RequestAutoclickScrollableBoundsForPoint(gfx::Point& point_in_screen);
void SetFloatingMenuPosition(FloatingMenuPosition position);
FloatingMenuPosition GetFloatingMenuPosition();
FloatingAccessibilityController* GetFloatingMenuControllerForTesting();
// Update the autoclick menu bounds if necessary. This may need to happen when
// the display work area changes, or if system ui regions change (like the
// virtual keyboard position).
......@@ -289,6 +294,8 @@ class ASH_EXPORT AccessibilityControllerImpl : public AccessibilityController,
return tablet_mode_shelf_navigation_buttons_enabled_;
}
void ShowFloatingMenuIfEnabled() override;
bool dictation_active() const { return dictation_active_; }
// Returns true if accessibility shortcuts have been disabled.
......@@ -417,6 +424,7 @@ class ASH_EXPORT AccessibilityControllerImpl : public AccessibilityController,
void UpdateAutoclickStabilizePositionFromPref();
void UpdateAutoclickMovementThresholdFromPref();
void UpdateAutoclickMenuPositionFromPref();
void UpdateFloatingMenuPositionFromPref();
void UpdateLargeCursorFromPref();
void UpdateSwitchAccessKeyCodesFromPref(SwitchAccessCommand command);
void UpdateSwitchAccessAutoScanEnabledFromPref();
......@@ -461,6 +469,9 @@ class ASH_EXPORT AccessibilityControllerImpl : public AccessibilityController,
std::unique_ptr<AccessibilityHighlightController>
accessibility_highlight_controller_;
// Used to display accessibility floating menu.
std::unique_ptr<FloatingAccessibilityController> floating_menu_controller_;
// Used to force the backlights off to darken the screen.
std::unique_ptr<ScopedBacklightsForcedOff> scoped_backlights_forced_off_;
......
......@@ -108,6 +108,9 @@ class ASH_PUBLIC_EXPORT AccessibilityController {
// Disables restoring of recommended policy values.
virtual void DisablePolicyRecommendationRestorerForTesting() {}
// Shows floating accessibility menu if it was enabled by policy.
virtual void ShowFloatingMenuIfEnabled() {}
protected:
AccessibilityController();
virtual ~AccessibilityController();
......
......@@ -122,9 +122,9 @@ enum class AutoclickEventType {
kMaxValue = kScroll
};
// Display location of the on-screen floating menus used by accessibility features(e.g. the
// Automatic Clicks) . These values are written to prefs so they should not be changed. New values
// should be added at the end.
// Display location of the on-screen floating menus used by accessibility
// features(e.g. the Automatic Clicks) . These values are written to prefs so
// they should not be changed. New values should be added at the end.
enum class FloatingMenuPosition {
// The bottom right of the screen.
kBottomRight,
......
......@@ -60,6 +60,10 @@ constexpr int kDefaultAutoclickMovementThreshold = 20;
constexpr FloatingMenuPosition kDefaultAutoclickMenuPosition =
FloatingMenuPosition::kSystemDefault;
// The default floating accessibility menu position.
constexpr FloatingMenuPosition kDefaultFloatingMenuPosition =
FloatingMenuPosition::kSystemDefault;
// The default frame color.
constexpr SkColor kDefaultFrameColor = SkColorSetRGB(0xFD, 0xFE, 0xFF);
......
......@@ -80,6 +80,9 @@ const char kAccessibilityCursorHighlightEnabled[] =
// A boolean pref which determines whether floating accessibility menu is
// enabled.
const char kAccessibilityFloatingMenuEnabled[] = "settings.a11y.floating_menu";
// Floating a11y menu position, a FloatingMenuPosition;
const char kAccessibilityFloatingMenuPosition[] =
"settings.a11y.floating_menu_position";
// A boolean pref which determines whether focus highlighting is enabled.
const char kAccessibilityFocusHighlightEnabled[] =
"settings.a11y.focus_highlight";
......
......@@ -32,6 +32,7 @@ ASH_PUBLIC_EXPORT extern const char kAccessibilityAutoclickMenuPosition[];
ASH_PUBLIC_EXPORT extern const char kAccessibilityCaretHighlightEnabled[];
ASH_PUBLIC_EXPORT extern const char kAccessibilityCursorHighlightEnabled[];
ASH_PUBLIC_EXPORT extern const char kAccessibilityFloatingMenuEnabled[];
ASH_PUBLIC_EXPORT extern const char kAccessibilityFloatingMenuPosition[];
ASH_PUBLIC_EXPORT extern const char kAccessibilityFocusHighlightEnabled[];
ASH_PUBLIC_EXPORT extern const char kAccessibilitySelectToSpeakEnabled[];
ASH_PUBLIC_EXPORT extern const char kAccessibilitySwitchAccessEnabled[];
......
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "ash/system/accessibility/floating_accessibility_controller.h"
#include "ash/public/cpp/shell_window_ids.h"
#include "ash/session/session_controller_impl.h"
#include "ash/shell.h"
#include "ash/system/tray/tray_background_view.h"
#include "ash/system/tray/tray_constants.h"
#include "ash/wm/collision_detection/collision_detection_utils.h"
#include "ash/wm/work_area_insets.h"
#include "base/logging.h"
#include "ui/compositor/scoped_layer_animation_settings.h"
namespace ash {
namespace {
constexpr int kFloatingMenuHeight = 64;
constexpr base::TimeDelta kAnimationDuration =
base::TimeDelta::FromMilliseconds(150);
FloatingMenuPosition DefaultSystemPosition() {
return base::i18n::IsRTL() ? FloatingMenuPosition::kBottomLeft
: FloatingMenuPosition::kBottomRight;
}
} // namespace
FloatingAccessibilityController::FloatingAccessibilityController() {
Shell::Get()->locale_update_controller()->AddObserver(this);
}
FloatingAccessibilityController::~FloatingAccessibilityController() {
Shell::Get()->locale_update_controller()->RemoveObserver(this);
if (bubble_widget_ && !bubble_widget_->IsClosed())
bubble_widget_->CloseNow();
}
void FloatingAccessibilityController::Show(FloatingMenuPosition position) {
// Kiosk check.
if (!Shell::Get()->session_controller()->IsRunningInAppMode()) {
NOTREACHED()
<< "Floating accessibility menu can only be run in a kiosk session.";
return;
}
DCHECK(!bubble_view_);
position_ = position;
TrayBubbleView::InitParams init_params;
init_params.delegate = this;
// We need our view to be on the same level as the autoclicks menu, so neither
// of them is overlapping each other.
init_params.parent_window = Shell::GetContainer(
Shell::GetPrimaryRootWindow(), kShellWindowId_AutoclickContainer);
init_params.anchor_mode = TrayBubbleView::AnchorMode::kRect;
// The widget's shadow is drawn below and on the sides of the view, with a
// width of kCollisionWindowWorkAreaInsetsDp. Set the top inset to 0 to ensure
// the scroll view is drawn at kCollisionWindowWorkAreaInsetsDp above the
// bubble menu when the position is at the bottom of the screen. The space
// between the bubbles belongs to the scroll view bubble's shadow.
init_params.insets = gfx::Insets(0, kCollisionWindowWorkAreaInsetsDp,
kCollisionWindowWorkAreaInsetsDp,
kCollisionWindowWorkAreaInsetsDp);
init_params.max_height = kFloatingMenuHeight;
init_params.corner_radius = kUnifiedTrayCornerRadius;
init_params.has_shadow = false;
init_params.translucent = true;
bubble_view_ = new FloatingAccessibilityBubbleView(init_params);
menu_view_ = new FloatingAccessibilityView(this);
menu_view_->SetBorder(
views::CreateEmptyBorder(kUnifiedTopShortcutSpacing, 0, 0, 0));
bubble_view_->AddChildView(menu_view_);
menu_view_->SetPaintToLayer();
menu_view_->layer()->SetFillsBoundsOpaquely(false);
bubble_widget_ = views::BubbleDialogDelegateView::CreateBubble(bubble_view_);
TrayBackgroundView::InitializeBubbleAnimations(bubble_widget_);
bubble_view_->InitializeAndShowBubble();
SetMenuPosition(position_);
}
void FloatingAccessibilityController::SetMenuPosition(
FloatingMenuPosition new_position) {
if (!menu_view_ || !bubble_view_ || !bubble_widget_)
return;
// Update the menu view's UX if the position has changed, or if it's not the
// default position (because that can change with language direction).
if (position_ != new_position ||
new_position == FloatingMenuPosition::kSystemDefault) {
menu_view_->SetMenuPosition(new_position);
}
position_ = new_position;
// If this is the default system position, pick the position based on the
// language direction.
if (new_position == FloatingMenuPosition::kSystemDefault)
new_position = DefaultSystemPosition();
// Calculates the ideal bounds.
// TODO(katie): Support multiple displays: draw the menu on whichever display
// the cursor is on.
aura::Window* window = Shell::GetPrimaryRootWindow();
gfx::Rect work_area =
WorkAreaInsets::ForWindow(window)->user_work_area_bounds();
gfx::Rect new_bounds;
gfx::Size preferred_size = menu_view_->GetPreferredSize();
switch (new_position) {
case FloatingMenuPosition::kBottomRight:
new_bounds = gfx::Rect(work_area.right() - preferred_size.width(),
work_area.bottom() - preferred_size.height(),
preferred_size.width(), preferred_size.height());
break;
case FloatingMenuPosition::kBottomLeft:
new_bounds =
gfx::Rect(work_area.x(), work_area.bottom() - preferred_size.height(),
preferred_size.width(), preferred_size.height());
break;
case FloatingMenuPosition::kTopLeft:
// Because there is no inset at the top of the widget, add
// 2 * kCollisionWindowWorkAreaInsetsDp to the top of the work area.
// to ensure correct padding.
new_bounds = gfx::Rect(
work_area.x(), work_area.y() + 2 * kCollisionWindowWorkAreaInsetsDp,
preferred_size.width(), preferred_size.height());
break;
case FloatingMenuPosition::kTopRight:
// Because there is no inset at the top of the widget, add
// 2 * kCollisionWindowWorkAreaInsetsDp to the top of the work area.
// to ensure correct padding.
new_bounds =
gfx::Rect(work_area.right() - preferred_size.width(),
work_area.y() + 2 * kCollisionWindowWorkAreaInsetsDp,
preferred_size.width(), preferred_size.height());
break;
case FloatingMenuPosition::kSystemDefault:
return;
}
gfx::Rect resting_bounds =
CollisionDetectionUtils::AdjustToFitMovementAreaByGravity(
display::Screen::GetScreen()->GetDisplayNearestWindow(
bubble_widget_->GetNativeWindow()),
new_bounds);
// Un-inset the bounds to get the widget's bounds, which includes the drop
// shadow.
resting_bounds.Inset(-kCollisionWindowWorkAreaInsetsDp, 0,
-kCollisionWindowWorkAreaInsetsDp,
-kCollisionWindowWorkAreaInsetsDp);
if (bubble_widget_->GetWindowBoundsInScreen() == resting_bounds)
return;
ui::ScopedLayerAnimationSettings settings(
bubble_widget_->GetLayer()->GetAnimator());
settings.SetPreemptionStrategy(
ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET);
settings.SetTransitionDuration(kAnimationDuration);
settings.SetTweenType(gfx::Tween::EASE_OUT);
bubble_widget_->SetBounds(resting_bounds);
}
void FloatingAccessibilityController::OnDetailedMenuEnabled(bool enabled) {
// TODO(crbug.com/1061068): Implement detailed menu view logic.
detailed_view_shown_ = enabled;
}
void FloatingAccessibilityController::BubbleViewDestroyed() {
bubble_view_ = nullptr;
bubble_widget_ = nullptr;
menu_view_ = nullptr;
}
void FloatingAccessibilityController::OnLocaleChanged() {
// Layout update is needed when language changes between LTR and RTL, if the
// position is the system default.
if (position_ == FloatingMenuPosition::kSystemDefault)
SetMenuPosition(position_);
}
} // namespace ash
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef ASH_SYSTEM_ACCESSIBILITY_FLOATING_ACCESSIBILITY_CONTROLLER_H_
#define ASH_SYSTEM_ACCESSIBILITY_FLOATING_ACCESSIBILITY_CONTROLLER_H_
#include "ash/public/cpp/accessibility_controller_enums.h"
#include "ash/public/cpp/ash_constants.h"
#include "ash/system/accessibility/floating_accessibility_view.h"
#include "ash/system/locale/locale_update_controller_impl.h"
namespace ash {
class FloatingAccessibilityView;
// Controls the floating accessibility menu.
class FloatingAccessibilityController
: public FloatingAccessibilityView::Delegate,
public TrayBubbleView::Delegate,
public LocaleChangeObserver {
public:
FloatingAccessibilityController();
FloatingAccessibilityController(const FloatingAccessibilityController&) =
delete;
FloatingAccessibilityController& operator=(
const FloatingAccessibilityController&) = delete;
~FloatingAccessibilityController() override;
// Starts showing the floating menu when called.
void Show(FloatingMenuPosition position);
void SetMenuPosition(FloatingMenuPosition new_position);
private:
friend class FloatingAccessibilityControllerTest;
// FloatingAccessibilityView::Delegate:
void OnDetailedMenuEnabled(bool enabled) override;
// TrayBubbleView::Delegate:
void BubbleViewDestroyed() override;
// LocaleChangeObserver:
void OnLocaleChanged() override;
FloatingAccessibilityView* menu_view_ = nullptr;
FloatingAccessibilityBubbleView* bubble_view_ = nullptr;
views::Widget* bubble_widget_ = nullptr;
bool detailed_view_shown_ = false;
FloatingMenuPosition position_ = kDefaultFloatingMenuPosition;
};
} // namespace ash
#endif // ASH_SYSTEM_ACCESSIBILITY_FLOATING_ACCESSIBILITY_CONTROLLER_H_
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "ash/system/accessibility/floating_accessibility_controller.h"
#include "ash/accessibility/accessibility_controller_impl.h"
#include "ash/public/cpp/session/session_types.h"
#include "ash/session/session_controller_impl.h"
#include "ash/shell.h"
#include "ash/test/ash_test_base.h"
namespace ash {
namespace {
// A buffer for checking whether the menu view is near this region of the
// screen. This buffer allows for things like padding and the shelf size,
// but is still smaller than half the screen size, so that we can check the
// general corner in which the menu is displayed.
const int kMenuViewBoundsBuffer = 100;
ui::GestureEvent CreateTapEvent() {
return ui::GestureEvent(0, 0, 0, base::TimeTicks(),
ui::GestureEventDetails(ui::ET_GESTURE_TAP));
}
} // namespace
class FloatingAccessibilityControllerTest : public AshTestBase {
public:
AccessibilityControllerImpl* accessibility_controller() {
return Shell::Get()->accessibility_controller();
}
FloatingAccessibilityController* controller() {
return accessibility_controller()->GetFloatingMenuControllerForTesting();
}
FloatingMenuPosition menu_position() { return controller()->position_; }
FloatingAccessibilityView* menu_view() {
return controller() ? controller()->menu_view_ : nullptr;
}
bool detailed_view_shown() { return controller()->detailed_view_shown_; }
views::View* GetMenuButton(FloatingAccessibilityView::ButtonId button_id) {
FloatingAccessibilityView* view = menu_view();
if (!view)
return nullptr;
return view->GetViewByID(static_cast<int>(button_id));
}
void SetUpKioskSession() {
SessionInfo info;
info.is_running_in_app_mode = true;
Shell::Get()->session_controller()->SetSessionInfo(info);
}
gfx::Rect GetMenuViewBounds() {
FloatingAccessibilityView* view = menu_view();
return view ? view->GetBoundsInScreen()
: gfx::Rect(-kMenuViewBoundsBuffer, -kMenuViewBoundsBuffer);
}
void Show() { accessibility_controller()->ShowFloatingMenuIfEnabled(); }
void SetUpVisibleMenu() {
SetUpKioskSession();
accessibility_controller()->floating_menu().SetEnabled(true);
accessibility_controller()->ShowFloatingMenuIfEnabled();
}
};
TEST_F(FloatingAccessibilityControllerTest, MenuIsNotShownWhenNotEnabled) {
accessibility_controller()->ShowFloatingMenuIfEnabled();
EXPECT_EQ(controller(), nullptr);
}
TEST_F(FloatingAccessibilityControllerTest, ShowingMenu) {
SetUpKioskSession();
accessibility_controller()->floating_menu().SetEnabled(true);
accessibility_controller()->ShowFloatingMenuIfEnabled();
EXPECT_TRUE(controller());
EXPECT_EQ(menu_position(),
accessibility_controller()->GetFloatingMenuPosition());
}
TEST_F(FloatingAccessibilityControllerTest, CanChangePosition) {
SetUpVisibleMenu();
accessibility_controller()->SetFloatingMenuPosition(
FloatingMenuPosition::kTopRight);
// Get the full root window bounds to test the position.
gfx::Rect window_bounds = Shell::GetPrimaryRootWindow()->bounds();
// Test cases rotate clockwise.
const struct {
gfx::Point expected_location;
FloatingMenuPosition expected_position;
} kTestCases[] = {
{gfx::Point(window_bounds.width(), window_bounds.height()),
FloatingMenuPosition::kBottomRight},
{gfx::Point(0, window_bounds.height()),
FloatingMenuPosition::kBottomLeft},
{gfx::Point(0, 0), FloatingMenuPosition::kTopLeft},
{gfx::Point(window_bounds.width(), 0), FloatingMenuPosition::kTopRight},
};
// Find the autoclick menu position button.
views::View* button =
GetMenuButton(FloatingAccessibilityView::ButtonId::kPosition);
ASSERT_TRUE(button) << "No position button found.";
// Loop through all positions twice.
for (int i = 0; i < 2; i++) {
for (const auto& test : kTestCases) {
SCOPED_TRACE(
base::StringPrintf("Testing position #[%d]", test.expected_position));
// Tap the position button.
ui::GestureEvent event = CreateTapEvent();
button->OnGestureEvent(&event);
// Pref change happened.
EXPECT_EQ(test.expected_position, menu_position());
// Menu is in generally the correct screen location.
EXPECT_LT(
GetMenuViewBounds().ManhattanDistanceToPoint(test.expected_location),
kMenuViewBoundsBuffer);
}
}
}
TEST_F(FloatingAccessibilityControllerTest, DetailedViewToggle) {
SetUpVisibleMenu();
// Find the autoclick menu position button.
views::View* button =
GetMenuButton(FloatingAccessibilityView::ButtonId::kSettingsList);
ASSERT_TRUE(button) << "No accessibility features list button found.";
EXPECT_FALSE(detailed_view_shown());
ui::GestureEvent event = CreateTapEvent();
button->OnGestureEvent(&event);
EXPECT_TRUE(detailed_view_shown());
event = CreateTapEvent();
button->OnGestureEvent(&event);
EXPECT_FALSE(detailed_view_shown());
}
TEST_F(FloatingAccessibilityControllerTest, LocaleChangeObserver) {
SetUpVisibleMenu();
gfx::Rect window_bounds = Shell::GetPrimaryRootWindow()->bounds();
// RTL should position the menu on the bottom left.
base::i18n::SetICUDefaultLocale("he");
// Trigger the LocaleChangeObserver, which should cause a layout of the menu.
ash::LocaleUpdateController::Get()->OnLocaleChanged(
"en", "en", "he", base::DoNothing::Once<ash::LocaleNotificationResult>());
EXPECT_TRUE(base::i18n::IsRTL());
EXPECT_LT(
GetMenuViewBounds().ManhattanDistanceToPoint(window_bounds.bottom_left()),
kMenuViewBoundsBuffer);
// LTR should position the menu on the bottom right.
base::i18n::SetICUDefaultLocale("en");
ash::LocaleUpdateController::Get()->OnLocaleChanged(
"he", "he", "en", base::DoNothing::Once<ash::LocaleNotificationResult>());
EXPECT_FALSE(base::i18n::IsRTL());
EXPECT_LT(GetMenuViewBounds().ManhattanDistanceToPoint(
window_bounds.bottom_right()),
kMenuViewBoundsBuffer);
}
} // namespace ash
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "ash/system/accessibility/floating_accessibility_view.h"
#include "ash/accessibility/accessibility_controller_impl.h"
#include "ash/keyboard/ui/keyboard_ui_controller.h"
#include "ash/public/cpp/system_tray.h"
#include "ash/resources/vector_icons/vector_icons.h"
#include "ash/root_window_controller.h"
#include "ash/shelf/shelf.h"
#include "ash/shell.h"
#include "ash/strings/grit/ash_strings.h"
#include "ash/style/ash_color_provider.h"
#include "ash/system/accessibility/floating_menu_button.h"
#include "ash/system/tray/tray_constants.h"
#include "ui/views/controls/separator.h"
#include "ui/views/layout/box_layout.h"
namespace ash {
namespace {
constexpr int kPanelPositionButtonPadding = 16;
constexpr int kPanelPositionButtonSize = 36;
constexpr int kSeparatorHeight = 16;
std::unique_ptr<views::Separator> CreateSeparator() {
auto separator = std::make_unique<views::Separator>();
separator->SetColor(AshColorProvider::Get()->GetContentLayerColor(
AshColorProvider::ContentLayerType::kSeparator,
AshColorProvider::AshColorMode::kDark));
separator->SetPreferredHeight(kSeparatorHeight);
int total_height = kUnifiedTopShortcutSpacing * 2 + kTrayItemSize;
int separator_spacing = (total_height - kSeparatorHeight) / 2;
separator->SetBorder(views::CreateEmptyBorder(
separator_spacing - kUnifiedTopShortcutSpacing, 0, separator_spacing, 0));
return separator;
}
std::unique_ptr<views::View> CreateButtonRowContainer() {
auto button_container = std::make_unique<views::View>();
button_container->SetLayoutManager(std::make_unique<views::BoxLayout>(
views::BoxLayout::Orientation::kHorizontal,
gfx::Insets(0, kPanelPositionButtonPadding, kPanelPositionButtonPadding,
kPanelPositionButtonPadding),
kPanelPositionButtonPadding));
return button_container;
}
} // namespace
FloatingAccessibilityBubbleView::FloatingAccessibilityBubbleView(
const TrayBubbleView::InitParams& init_params)
: TrayBubbleView(init_params) {}
FloatingAccessibilityBubbleView::~FloatingAccessibilityBubbleView() = default;
bool FloatingAccessibilityBubbleView::IsAnchoredToStatusArea() const {
return false;
}
const char* FloatingAccessibilityBubbleView::GetClassName() const {
return "FloatingAccessibilityBubbleView";
}
FloatingAccessibilityView::FloatingAccessibilityView(Delegate* delegate)
: delegate_(delegate) {
std::unique_ptr<views::BoxLayout> layout = std::make_unique<views::BoxLayout>(
views::BoxLayout::Orientation::kHorizontal, gfx::Insets(), 0);
SetLayoutManager(std::move(layout));
// TODO(crbug.com/1061068): Add buttons view that represents enabled features.
std::unique_ptr<views::View> tray_button_container =
CreateButtonRowContainer();
a11y_tray_button_ =
tray_button_container->AddChildView(std::make_unique<FloatingMenuButton>(
this, kUnifiedMenuAccessibilityIcon,
IDS_ASH_STATUS_TRAY_ACCESSIBILITY,
/*flip_for_rtl*/ true, kTrayItemSize));
std::unique_ptr<views::View> position_button_container =
CreateButtonRowContainer();
position_button_ = position_button_container->AddChildView(
std::make_unique<FloatingMenuButton>(
this, kAutoclickPositionBottomLeftIcon,
IDS_ASH_AUTOCLICK_OPTION_CHANGE_POSITION, /*flip_for_rtl*/ false,
kPanelPositionButtonSize, false));
AddChildView(std::move(tray_button_container));
AddChildView(CreateSeparator());
AddChildView(std::move(position_button_container));
// Set view IDs for testing.
position_button_->SetId(static_cast<int>(ButtonId::kPosition));
a11y_tray_button_->SetId(static_cast<int>(ButtonId::kSettingsList));
}
FloatingAccessibilityView::~FloatingAccessibilityView() {}
void FloatingAccessibilityView::SetMenuPosition(FloatingMenuPosition position) {
switch (position) {
case FloatingMenuPosition::kBottomRight:
position_button_->SetVectorIcon(kAutoclickPositionBottomRightIcon);
return;
case FloatingMenuPosition::kBottomLeft:
position_button_->SetVectorIcon(kAutoclickPositionBottomLeftIcon);
return;
case FloatingMenuPosition::kTopLeft:
position_button_->SetVectorIcon(kAutoclickPositionTopLeftIcon);
return;
case FloatingMenuPosition::kTopRight:
position_button_->SetVectorIcon(kAutoclickPositionTopRightIcon);
return;
case FloatingMenuPosition::kSystemDefault:
position_button_->SetVectorIcon(base::i18n::IsRTL()
? kAutoclickPositionBottomLeftIcon
: kAutoclickPositionBottomRightIcon);
return;
}
}
void FloatingAccessibilityView::ButtonPressed(views::Button* sender,
const ui::Event& event) {
if (sender == a11y_tray_button_) {
delegate_->OnDetailedMenuEnabled(!a11y_tray_button_->IsToggled());
a11y_tray_button_->SetToggled(!a11y_tray_button_->IsToggled());
return;
}
if (sender == position_button_) {
FloatingMenuPosition new_position;
// Rotate clockwise throughout the screen positions.
switch (
Shell::Get()->accessibility_controller()->GetFloatingMenuPosition()) {
case FloatingMenuPosition::kBottomRight:
new_position = FloatingMenuPosition::kBottomLeft;
break;
case FloatingMenuPosition::kBottomLeft:
new_position = FloatingMenuPosition::kTopLeft;
break;
case FloatingMenuPosition::kTopLeft:
new_position = FloatingMenuPosition::kTopRight;
break;
case FloatingMenuPosition::kTopRight:
new_position = FloatingMenuPosition::kBottomRight;
break;
case FloatingMenuPosition::kSystemDefault:
new_position = base::i18n::IsRTL() ? FloatingMenuPosition::kTopLeft
: FloatingMenuPosition::kBottomLeft;
break;
}
Shell::Get()->accessibility_controller()->SetFloatingMenuPosition(
new_position);
}
return;
}
const char* FloatingAccessibilityView::GetClassName() const {
return "AccessiblityFloatingView";
}
} // namespace ash
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef ASH_SYSTEM_ACCESSIBILITY_FLOATING_ACCESSIBILITY_VIEW_H_
#define ASH_SYSTEM_ACCESSIBILITY_FLOATING_ACCESSIBILITY_VIEW_H_
#include <vector>
#include "ash/public/cpp/accessibility_controller_enums.h"
#include "ash/shell_observer.h"
#include "ash/system/tray/tray_bubble_view.h"
#include "ui/views/controls/button/button.h"
namespace ash {
class FloatingMenuButton;
class FloatingAccessibilityBubbleView : public TrayBubbleView {
public:
explicit FloatingAccessibilityBubbleView(
const TrayBubbleView::InitParams& init_params);
FloatingAccessibilityBubbleView(const FloatingAccessibilityBubbleView&) =
delete;
FloatingAccessibilityBubbleView& operator=(
const FloatingAccessibilityBubbleView&) = delete;
~FloatingAccessibilityBubbleView() override;
// TrayBubbleView:
bool IsAnchoredToStatusArea() const override;
// views::View:
const char* GetClassName() const override;
};
// This floating view displays the currently enabled accessibility options,
// along with buttons to configure them.
// ---- Layout:
// ---- ?[Dictation] ?[SelectToSpeak] ?[VirtualKeyboard]
// ---- | [Open settings list]
// ---- | [Change menu location]
class FloatingAccessibilityView : public views::View,
public views::ButtonListener {
public:
// Used for testing. Starts 1 because views IDs should not be 0.
enum ButtonId {
kPosition = 1,
kSettingsList = 2,
kDictation = 3,
kSelectToSpeak = 4,
kVirtualKeyboard = 5,
};
class Delegate {
public:
// When the user click on the settings list button.
virtual void OnDetailedMenuEnabled(bool enabled) {}
virtual ~Delegate() = default;
};
FloatingAccessibilityView(Delegate* delegate);
FloatingAccessibilityView& operator=(const FloatingAccessibilityView&) =
delete;
~FloatingAccessibilityView() override;
FloatingAccessibilityView(const FloatingAccessibilityView&) = delete;
void SetMenuPosition(FloatingMenuPosition position);
private:
// views::ButtonListener:
void ButtonPressed(views::Button* sender, const ui::Event& event) override;
// views::View:
const char* GetClassName() const override;
// Button to list all available features.
FloatingMenuButton* a11y_tray_button_ = nullptr;
// Button to move the view around corners.
FloatingMenuButton* position_button_ = nullptr;
Delegate* const delegate_;
};
} // namespace ash
#endif // ASH_SYSTEM_ACCESSIBILITY_FLOATING_ACCESSIBILITY_VIEW_H_
......@@ -7,6 +7,7 @@
#include <errno.h>
#include <signal.h>
#include "ash/public/cpp/accessibility_controller.h"
#include "base/bind.h"
#include "base/lazy_instance.h"
#include "base/location.h"
......@@ -66,6 +67,13 @@ void RebootDevice() {
power_manager::REQUEST_RESTART_OTHER, "kiosk app session");
}
void StartFloatingAccessibilityMenu() {
ash::AccessibilityController* accessibility_controller =
ash::AccessibilityController::Get();
if (accessibility_controller)
accessibility_controller->ShowFloatingMenuIfEnabled();
}
// Sends a SIGFPE signal to plugin subprocesses that matches |child_ids|
// to trigger a dump.
void DumpPluginProcessOnIOThread(const std::set<int>& child_ids) {
......@@ -207,6 +215,8 @@ void AppSession::Init(Profile* profile, const std::string& app_id) {
plugin_handler_ = std::make_unique<KioskSessionPluginHandler>(this);
StartFloatingAccessibilityMenu();
// For a demo app, we don't need to either setup the update service or
// the idle app name notification.
if (DemoAppLauncher::IsDemoAppSession(
......@@ -240,6 +250,8 @@ void AppSession::InitForWebKiosk(Browser* browser) {
// the browser window was closed.
browser_window_handler_ =
std::make_unique<BrowserWindowHandler>(this, browser);
StartFloatingAccessibilityMenu();
}
void AppSession::SetAttemptUserExitForTesting(base::OnceClosure closure) {
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment