0

capture_selfie_cam: Add a11y alert for snapping and resizing camera

- Trigger a11y alert when focusing the camera preview.
- Trigger a11y alert when snapping the camera preview, either through
  keyboard, mouse or gesture. Alert will still be triggered even though
  the snap position does not change through the operations.
- Trigger a11y alert when pressing the resize button to
  collapse/expand the camera preview.

Bug: 1302690
Change-Id: I707424f67e428da833be093779d6a6521cd892ea
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3569627
Reviewed-by: Ahmed Fakhry <afakhry@chromium.org>
Commit-Queue: Min Chen <minch@chromium.org>
Cr-Commit-Position: refs/heads/main@{#994458}
This commit is contained in:
minch
2022-04-21 00:42:38 +00:00
committed by Chromium LUCI CQ
parent dddccc7aaf
commit 4d7222fecf
12 changed files with 109 additions and 46 deletions

@ -4025,6 +4025,21 @@ Here are some things you can try to get started.
<message name="IDS_ASH_SCREEN_CAPTURE_SELECTED_CAMERA_CHANGED" desc="Alert spoken by screen readers when the selected camera gets changed.">
Camera input set to <ph name="CAMERA_NAME">$1</ph>.
</message>
<message name="IDS_ASH_SCREEN_CAPTURE_CAMERA_PREVIEW_FOCUSED" desc="Alert spoken by screen readers when camera preview is focused.">
Camera preview, press control + arrow keys to move the preview to a different corner
</message>
<message name="IDS_ASH_SCREEN_CAPTURE_CAMERA_PREVIEW_SNAPPED_TO_UPPER_RIGHT" desc="Alert spoken by screen readers when camera preview being snapped to upper right corner">
Camera snapped to upper right corner
</message>
<message name="IDS_ASH_SCREEN_CAPTURE_CAMERA_PREVIEW_SNAPPED_TO_UPPER_LEFT" desc="Alert spoken by screen readers when camera preview being snapped to upper left corner">
Camera snapped to upper left corner
</message>
<message name="IDS_ASH_SCREEN_CAPTURE_CAMERA_PREVIEW_SNAPPED_TO_LOWER_RIGHT" desc="Alert spoken by screen readers when camera preview being snapped to lower right corner">
Camera snapped to lower right corner
</message>
<message name="IDS_ASH_SCREEN_CAPTURE_CAMERA_PREVIEW_SNAPPED_TO_LOWER_LEFT" desc="Alert spoken by screen readers when camera preview being snapped to lower left corner">
Camera snapped to lower left corner
</message>
<message name="IDS_ASH_SCREEN_CAPTURE_NAVIGATION_ALERT_RECORD_MICROPHONE" desc="Alert spoken by screen readers when user tabs to record microphone setting.">
Record microphone <ph name="CURRENT_STATE">$1<ex>off</ex></ph>, Press enter to turn microphone recording <ph name="NEW_STATE">$2<ex>on</ex></ph>
</message>

@ -0,0 +1 @@
640369267721cc21ec00973d27469617cd9d21e6

@ -0,0 +1 @@
261a7e1172786d25c8032996168d7d217e62c578

@ -0,0 +1 @@
69522bd01ed5159c1c327ce2e9e37fb5ce76004a

@ -0,0 +1 @@
b6a84be1d7df191e70a35481868d40e6a3a046de

@ -0,0 +1 @@
44eb3cbc3e27e79645ce0cee1ebe573b25b249d4

@ -49,6 +49,7 @@
#include "ui/views/widget/widget.h"
#include "ui/wm/core/coordinate_conversion.h"
#include "ui/wm/core/window_properties.h"
#include "ui/wm/core/window_util.h"
namespace ash {
@ -179,10 +180,6 @@ views::Widget::InitParams CreateWidgetParams(const gfx::Rect& bounds) {
views::Widget::InitParams params(views::Widget::InitParams::TYPE_POPUP);
params.parent = CaptureModeController::Get()->GetCameraPreviewParentWindow();
params.bounds = bounds;
// Need to set `params.child` to true here, otherwise camera preview widget
// will be added as a transient child to `params.parent`. For more details,
// please check `NativeWidgetAura::InitNativeWidget`.
params.child = true;
params.name = "CameraPreviewWidget";
return params;
}
@ -278,6 +275,21 @@ gfx::Size CalculatePreviewInitialSize() {
return gfx::Size(max_shorter_side, max_shorter_side);
}
// Returns the appropriate `message_id` for ChromeVox alert on setting camera
// preview snap position.
int GetMessageIdForSnapPosition(CameraPreviewSnapPosition snap_position) {
switch (snap_position) {
case CameraPreviewSnapPosition::kTopRight:
return IDS_ASH_SCREEN_CAPTURE_CAMERA_PREVIEW_SNAPPED_TO_UPPER_RIGHT;
case CameraPreviewSnapPosition::kTopLeft:
return IDS_ASH_SCREEN_CAPTURE_CAMERA_PREVIEW_SNAPPED_TO_UPPER_LEFT;
case CameraPreviewSnapPosition::kBottomRight:
return IDS_ASH_SCREEN_CAPTURE_CAMERA_PREVIEW_SNAPPED_TO_LOWER_RIGHT;
case CameraPreviewSnapPosition::kBottomLeft:
return IDS_ASH_SCREEN_CAPTURE_CAMERA_PREVIEW_SNAPPED_TO_LOWER_LEFT;
}
}
// Defines a window targeter that will be installed on the camera preview
// widget's window so that we can allow located events outside of the camera
// preview circle to go through and not be consumed by the camera preview. This
@ -451,15 +463,21 @@ void CaptureModeCameraController::MaybeReparentPreviewWidget() {
if (!camera_preview_widget_)
return;
// Remove the camera preview from its transient parent if it has one. And
// reparenting it to the corresponding parent again. This is done to keep
// the camera preview in a correct state that is not a transient child of
// its parent.
auto* native_window = camera_preview_widget_->GetNativeWindow();
if (auto* transient_parent = wm::GetTransientParent(native_window))
wm::RemoveTransientChild(transient_parent, native_window);
const bool was_visible_before = camera_preview_widget_->IsVisible();
auto* controller = CaptureModeController::Get();
DCHECK(!controller->is_recording_in_progress());
auto* parent = controller->GetCameraPreviewParentWindow();
DCHECK(parent);
auto* native_window = camera_preview_widget_->GetNativeWindow();
if (parent != native_window->parent())
views::Widget::ReparentNativeView(native_window, parent);
DCHECK(!wm::GetTransientParent(native_window));
MaybeUpdatePreviewWidget();
if (was_visible_before != camera_preview_widget_->IsVisible()) {
@ -470,12 +488,15 @@ void CaptureModeCameraController::MaybeReparentPreviewWidget() {
}
void CaptureModeCameraController::SetCameraPreviewSnapPosition(
CameraPreviewSnapPosition value) {
if (camera_preview_snap_position_ == value)
return;
CameraPreviewSnapPosition value,
bool animate) {
// Trigger a11y alert on setting camera preview snap position even though the
// snap position may actually not change.
capture_mode_util::TriggerAccessibilityAlert(
GetMessageIdForSnapPosition(value));
camera_preview_snap_position_ = value;
MaybeUpdatePreviewWidget();
MaybeUpdatePreviewWidget(animate);
}
void CaptureModeCameraController::MaybeUpdatePreviewWidget(bool animate) {
@ -550,9 +571,8 @@ void CaptureModeCameraController::EndDraggingPreview(
const gfx::PointF& screen_location,
bool is_touch) {
ContinueDraggingPreview(screen_location);
UpdateSnapPositionOnDragEnded();
MaybeUpdatePreviewWidget(/*animate=*/true);
SetCameraPreviewSnapPosition(CalculateSnapPositionOnDragEnded(),
/*animate=*/true);
is_drag_in_progress_ = false;
camera_preview_view_->RefreshResizeButtonVisibility();
@ -775,7 +795,7 @@ void CaptureModeCameraController::RefreshCameraPreview() {
layer->SetFillsBoundsOpaquely(false);
layer->SetMasksToBounds(true);
MaybeUpdatePreviewWidget(/*animate=*/false);
MaybeReparentPreviewWidget();
}
DCHECK(camera_preview_view_);
@ -884,7 +904,8 @@ gfx::Rect CaptureModeCameraController::GetPreviewWidgetBoundsForSnapPosition(
return gfx::Rect(origin, preview_size);
}
void CaptureModeCameraController::UpdateSnapPositionOnDragEnded() {
CameraPreviewSnapPosition
CaptureModeCameraController::CalculateSnapPositionOnDragEnded() const {
const gfx::Point center_point_of_preview_widget =
GetCurrentBoundsMatchingConfineBoundsCoordinates().CenterPoint();
const gfx::Point center_point_of_confine_bounds =
@ -893,20 +914,20 @@ void CaptureModeCameraController::UpdateSnapPositionOnDragEnded() {
.CenterPoint();
if (center_point_of_preview_widget.x() < center_point_of_confine_bounds.x()) {
if (center_point_of_preview_widget.y() < center_point_of_confine_bounds.y())
camera_preview_snap_position_ = CameraPreviewSnapPosition::kTopLeft;
else
camera_preview_snap_position_ = CameraPreviewSnapPosition::kBottomLeft;
} else {
if (center_point_of_preview_widget.y() < center_point_of_confine_bounds.y())
camera_preview_snap_position_ = CameraPreviewSnapPosition::kTopRight;
else
camera_preview_snap_position_ = CameraPreviewSnapPosition::kBottomRight;
return center_point_of_preview_widget.y() <
center_point_of_confine_bounds.y()
? CameraPreviewSnapPosition::kTopLeft
: CameraPreviewSnapPosition::kBottomLeft;
}
return center_point_of_preview_widget.y() < center_point_of_confine_bounds.y()
? CameraPreviewSnapPosition::kTopRight
: CameraPreviewSnapPosition::kBottomRight;
}
gfx::Rect CaptureModeCameraController::
GetCurrentBoundsMatchingConfineBoundsCoordinates() {
gfx::Rect
CaptureModeCameraController::GetCurrentBoundsMatchingConfineBoundsCoordinates()
const {
aura::Window* preview_window = camera_preview_widget_->GetNativeWindow();
aura::Window* parent = preview_window->parent();
if (parent->GetProperty(wm::kUsesScreenCoordinatesKey))

@ -190,8 +190,10 @@ class ASH_EXPORT CaptureModeCameraController
void MaybeReparentPreviewWidget();
// Sets `camera_preview_snap_position_` and updates the preview widget's
// bounds accordingly.
void SetCameraPreviewSnapPosition(CameraPreviewSnapPosition value);
// bounds accordingly. If `animate` is set to true, the camera preview will
// animate to its new snap position.
void SetCameraPreviewSnapPosition(CameraPreviewSnapPosition value,
bool animate = false);
// Updates the bounds and visibility of `camera_preview_widget_` according to
// the current state of the capture surface within which the camera preview
@ -286,13 +288,12 @@ class ASH_EXPORT CaptureModeCameraController
const gfx::Size& preview_size,
CameraPreviewSnapPosition snap_position) const;
// Called by `EndDraggingPreview`, updating `camera_preview_snap_position_`
// according to the current position of the `camera_preview_view_`.
void UpdateSnapPositionOnDragEnded();
// Returns the new snap position of the camera preview on drag ended.
CameraPreviewSnapPosition CalculateSnapPositionOnDragEnded() const;
// Returns the current bounds of camemra preview widget that match the
// coordinate system of the confine bounds.
gfx::Rect GetCurrentBoundsMatchingConfineBoundsCoordinates();
gfx::Rect GetCurrentBoundsMatchingConfineBoundsCoordinates() const;
// Does post works for camera preview after RefreshCameraPreview(). It
// triggers a11y alert based on `was_preview_visible_before` and the current

@ -20,6 +20,7 @@
#include "ui/events/event.h"
#include "ui/gfx/geometry/point_f.h"
#include "ui/gfx/paint_vector_icon.h"
#include "ui/views/accessibility/view_accessibility.h"
#include "ui/views/animation/animation_builder.h"
#include "ui/views/background.h"
#include "ui/views/controls/highlight_path_generator.h"
@ -118,11 +119,11 @@ bool CameraPreviewView::MaybeHandleKeyEvent(const ui::KeyEvent* event) {
current_snap_position, /*going_up=*/key_code == ui::VKEY_UP);
}
if (new_snap_position == current_snap_position)
return false;
camera_controller_->SetCameraPreviewSnapPosition(new_snap_position);
return true;
// Still call `SetCameraPreviewSnapPosition` even if the snap position does
// not change. As we still want to trigger a11y alert in this case.
camera_controller_->SetCameraPreviewSnapPosition(new_snap_position,
/*animate=*/true);
return new_snap_position == current_snap_position;
}
void CameraPreviewView::RefreshResizeButtonVisibility() {
@ -236,6 +237,13 @@ void CameraPreviewView::Layout() {
gfx::RoundedCornersF(height() / 2.f));
}
void CameraPreviewView::GetAccessibleNodeData(ui::AXNodeData* node_data) {
views::View::GetAccessibleNodeData(node_data);
node_data->SetName(
l10n_util::GetStringUTF16(IDS_ASH_SCREEN_CAPTURE_CAMERA_PREVIEW_FOCUSED));
node_data->role = ax::mojom::Role::kVideo;
}
views::View* CameraPreviewView::GetView() {
return this;
}

@ -77,6 +77,7 @@ class CameraPreviewView
void OnMouseEntered(const ui::MouseEvent& event) override;
void OnMouseExited(const ui::MouseEvent& event) override;
void Layout() override;
void GetAccessibleNodeData(ui::AXNodeData* node_data) override;
// CaptureModeSessionFocusCycler::HighlightableView:
views::View* GetView() override;

@ -64,6 +64,7 @@
#include "ui/views/view_observer.h"
#include "ui/views/widget/widget.h"
#include "ui/wm/core/coordinate_conversion.h"
#include "ui/wm/core/window_util.h"
namespace ash {
@ -1230,13 +1231,15 @@ TEST_F(CaptureModeCameraTest, CameraPreviewWidgetAfterTypeSwitched) {
const auto* camera_preview_widget =
camera_controller->camera_preview_widget();
EXPECT_TRUE(camera_preview_widget);
auto* parent = camera_preview_widget->GetNativeWindow()->parent();
auto* camera_preview_window = camera_preview_widget->GetNativeWindow();
const auto* selected_window =
controller->capture_mode_session()->GetSelectedWindow();
ASSERT_EQ(parent, selected_window);
ASSERT_EQ(camera_preview_window->parent(), selected_window);
// Verify that camera preview is at the bottom right corner of the window.
VerifyPreviewAlignment(selected_window->GetBoundsInScreen());
// `camera_preview_window` should not have a transient parent.
EXPECT_FALSE(wm::GetTransientParent(camera_preview_window));
}
// Tests that audio and camera menu groups should be hidden from the settings
@ -1642,7 +1645,7 @@ TEST_F(CaptureModeCameraTest, FocusableCameraPreviewInFullscreen) {
// Tests that the camera preview is focusable in fullscreen capture.
auto* camera_preview_view = camera_controller->camera_preview_view();
auto* resize_button = GetPreviewResizeButton();
SendKey(ui::VKEY_TAB, event_generator, /*shift_down=*/false, /*count=*/6);
SendKey(ui::VKEY_TAB, event_generator, ui::EF_NONE, /*count=*/6);
EXPECT_EQ(FocusGroup::kCameraPreview, test_api.GetCurrentFocusGroup());
EXPECT_EQ(0u, test_api.GetCurrentFocusIndex());
EXPECT_TRUE(camera_preview_view->has_focus());
@ -1731,7 +1734,7 @@ TEST_F(CaptureModeCameraTest, FocusableCameraPreviewInRegion) {
// Tests that the camera preview is focusable in region capture.
auto* camera_preview_view = camera_controller->camera_preview_view();
auto* resize_button = GetPreviewResizeButton();
SendKey(ui::VKEY_TAB, event_generator, /*shift_down=*/false, /*count=*/15);
SendKey(ui::VKEY_TAB, event_generator, ui::EF_NONE, /*count=*/15);
EXPECT_EQ(FocusGroup::kCameraPreview, test_api.GetCurrentFocusGroup());
EXPECT_EQ(0u, test_api.GetCurrentFocusIndex());
EXPECT_TRUE(camera_preview_view->has_focus());
@ -1797,7 +1800,7 @@ TEST_F(CaptureModeCameraTest, FocusableCameraPreviewInWindow) {
// be shown inside it.
auto* camera_preview_view = camera_controller->camera_preview_view();
auto* resize_button = GetPreviewResizeButton();
SendKey(ui::VKEY_TAB, event_generator, /*shift_down=*/false, /*count=*/6);
SendKey(ui::VKEY_TAB, event_generator, ui::EF_NONE, /*count=*/6);
EXPECT_EQ(FocusGroup::kCaptureWindow, test_api.GetCurrentFocusGroup());
EXPECT_EQ(0u, test_api.GetCurrentFocusIndex());
EXPECT_TRUE(test_api.GetHighlightableWindow(window2.get())->has_focus());
@ -2504,6 +2507,15 @@ TEST_P(CaptureModeCameraPreviewTest, CameraPreviewDragToSnap) {
camera_controller->camera_preview_snap_position());
VerifyPreviewAlignment(GetCaptureBoundsInScreen());
// Drag the camera preview for a small distance. Tests that even though the
// snap position does not change, the preview should be snapped back to its
// previous position.
DragPreviewToPoint(preview_widget, {capture_bounds_center_point.x() + 20,
capture_bounds_center_point.y() + 20});
EXPECT_EQ(CameraPreviewSnapPosition::kBottomRight,
camera_controller->camera_preview_snap_position());
VerifyPreviewAlignment(GetCaptureBoundsInScreen());
// Drag and drop camera preview by mouse to the top right of the
// `capture_bounds_center_point`, verify that camera preview is snapped to
// the top right with correct position.

@ -1035,6 +1035,9 @@ void CaptureModeSession::OnKeyEvent(ui::KeyEvent* event) {
return;
}
if (event->type() != ui::ET_KEY_PRESSED)
return;
auto* camera_controller = controller_->camera_controller();
auto* camera_preview_view =
camera_controller ? camera_controller->camera_preview_view() : nullptr;
@ -1043,9 +1046,6 @@ void CaptureModeSession::OnKeyEvent(ui::KeyEvent* event) {
return;
}
if (event->type() != ui::ET_KEY_PRESSED)
return;
// We create an owned heap-allocated boolean to pass it to `deferred_runner`
// and hold a pointer to the boolean to be able to change its value to control
// whether the deferred runner should call `MaybeUpdateCaptureUisOpacity` or