#include "ash/annotator/annotator_controller.h"
#include "ash/annotator/annotation_tray.h"
#include "ash/annotator/annotator_metrics.h"
#include "ash/constants/ash_pref_names.h"
#include "ash/projector/projector_metrics.h"
#include "ash/public/cpp/annotator/annotator_tool.h"
#include "ash/root_window_controller.h"
#include "ash/session/session_controller_impl.h"
#include "ash/shell.h"
#include "ash/strings/grit/ash_strings.h"
#include "ash/system/status_area_widget.h"
#include "ash/system/tray/tray_container.h"
#include "ash/test/ash_test_base.h"
#include "ash/webui/annotator/test/mock_annotator_client.h"
#include "base/task/single_thread_task_runner.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/time/time.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/aura/window.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/events/gesture_detection/gesture_configuration.h"
#include "ui/events/test/event_generator.h"
#include "ui/gfx/geometry/point.h"
#include "ui/views/accessibility/view_accessibility.h"
#include "ui/views/controls/image_view.h"
namespace ash {
namespace {
constexpr char kAnnotatorMarkerColorHistogramName[] =
"Ash.Projector.MarkerColor.ClamshellMode";
constexpr char kProjectorToolbarHistogramName[] =
"Ash.Projector.Toolbar.ClamshellMode";
}
class MockAnnotatorObserver : public AnnotatorController::AnnotatorObserver {
public:
MockAnnotatorObserver() = default;
MockAnnotatorObserver(const MockAnnotatorObserver&) = delete;
MockAnnotatorObserver& operator=(const MockAnnotatorObserver&) = delete;
~MockAnnotatorObserver() override {}
MOCK_METHOD1(OnAnnotatorStateChanged, void(bool));
};
class AnnotatorControllerTest : public AshTestBase {
public:
AnnotatorControllerTest() = default;
AnnotatorControllerTest(const AnnotatorControllerTest&) = delete;
AnnotatorControllerTest& operator=(const AnnotatorControllerTest&) = delete;
void SetUp() override {
AshTestBase::SetUp();
auto* annotator_controller = Shell::Get()->annotator_controller();
annotator_controller->SetToolClient(&client_);
annotator_controller->AddObserver(&observer_);
}
void TearDown() override {
Shell::Get()->annotator_controller()->RemoveObserver(&observer_);
AshTestBase::TearDown();
}
AnnotatorController* annotator_controller() {
return Shell::Get()->annotator_controller();
}
MockAnnotatorObserver& observer() { return observer_; }
protected:
MockAnnotatorClient client_;
MockAnnotatorObserver observer_;
};
TEST_F(AnnotatorControllerTest, SetAnnotatorTool) {
base::HistogramTester histogram_tester;
AnnotatorTool tool;
tool.color = kAnnotatorDefaultPenColor;
EXPECT_CALL(client_, SetTool(tool));
annotator_controller()->SetAnnotatorTool(tool);
histogram_tester.ExpectUniqueSample(kAnnotatorMarkerColorHistogramName,
AnnotatorMarkerColor::kMagenta,
1);
}
TEST_F(AnnotatorControllerTest, RegisterAndUnregisterView) {
auto* annotation_tray = Shell::GetPrimaryRootWindowController()
->GetStatusAreaWidget()
->annotation_tray();
annotator_controller()->RegisterView(Shell::GetPrimaryRootWindow());
EXPECT_TRUE(annotation_tray->visible_preferred());
annotator_controller()->UnregisterView(Shell::GetPrimaryRootWindow());
EXPECT_FALSE(annotation_tray->visible_preferred());
}
TEST_F(AnnotatorControllerTest, RegisterAndUnregisterViewMultipleDisplays) {
UpdateDisplay("800x700,801+0-800x700");
aura::Window::Windows roots = Shell::GetAllRootWindows();
ASSERT_EQ(2u, roots.size());
auto* primary_display_tray = Shell::GetPrimaryRootWindowController()
->GetStatusAreaWidget()
->annotation_tray();
auto* external_display_tray = RootWindowController::ForWindow(roots[1])
->GetStatusAreaWidget()
->annotation_tray();
annotator_controller()->RegisterView(Shell::GetPrimaryRootWindow());
EXPECT_TRUE(primary_display_tray->visible_preferred());
EXPECT_FALSE(external_display_tray->visible_preferred());
annotator_controller()->UnregisterView(Shell::GetPrimaryRootWindow());
annotator_controller()->RegisterView(roots[1]);
EXPECT_FALSE(primary_display_tray->visible_preferred());
EXPECT_TRUE(external_display_tray->visible_preferred());
annotator_controller()->UnregisterView(roots[1]);
EXPECT_FALSE(primary_display_tray->visible_preferred());
EXPECT_FALSE(external_display_tray->visible_preferred());
}
TEST_F(AnnotatorControllerTest, UnregisterViewWhenAnnotatorIsEnabled) {
base::HistogramTester histogram_tester;
auto* annotation_tray = Shell::GetPrimaryRootWindowController()
->GetStatusAreaWidget()
->annotation_tray();
annotator_controller()->RegisterView(Shell::GetPrimaryRootWindow());
EXPECT_TRUE(annotation_tray->visible_preferred());
EXPECT_CALL(observer(), OnAnnotatorStateChanged(true));
annotator_controller()->CreateAnnotationOverlayForWindow(
Shell::GetPrimaryRootWindow(), std::nullopt);
annotator_controller()->EnableAnnotatorTool();
EXPECT_TRUE(annotator_controller()->is_annotator_enabled());
annotator_controller()->UnregisterView(Shell::GetPrimaryRootWindow());
EXPECT_FALSE(annotation_tray->visible_preferred());
histogram_tester.ExpectUniqueSample(kProjectorToolbarHistogramName,
ProjectorToolbar::kMarkerTool,
1);
}
TEST_F(AnnotatorControllerTest, EnableAnnotator) {
annotator_controller()->EnableAnnotatorTool();
EXPECT_FALSE(annotator_controller()->is_annotator_enabled());
EXPECT_CALL(observer(), OnAnnotatorStateChanged(true));
annotator_controller()->CreateAnnotationOverlayForWindow(
Shell::GetPrimaryRootWindow(), std::nullopt);
annotator_controller()->EnableAnnotatorTool();
EXPECT_TRUE(annotator_controller()->is_annotator_enabled());
}
TEST_F(AnnotatorControllerTest, DisableAnnotator) {
base::HistogramTester histogram_tester;
auto* annotation_tray = Shell::GetPrimaryRootWindowController()
->GetStatusAreaWidget()
->annotation_tray();
annotator_controller()->RegisterView(Shell::GetPrimaryRootWindow());
EXPECT_TRUE(annotation_tray->visible_preferred());
EXPECT_CALL(observer(), OnAnnotatorStateChanged(true));
annotator_controller()->CreateAnnotationOverlayForWindow(
Shell::GetPrimaryRootWindow(), std::nullopt);
annotator_controller()->EnableAnnotatorTool();
EXPECT_TRUE(annotator_controller()->is_annotator_enabled());
EXPECT_CALL(observer(), OnAnnotatorStateChanged(false));
annotator_controller()->DisableAnnotator();
EXPECT_FALSE(annotation_tray->visible_preferred());
EXPECT_FALSE(annotator_controller()->is_annotator_enabled());
}
TEST_F(AnnotatorControllerTest, EnablingDisablingMarker) {
base::HistogramTester histogram_tester;
EXPECT_CALL(observer(), OnAnnotatorStateChanged(true));
annotator_controller()->CreateAnnotationOverlayForWindow(
Shell::GetPrimaryRootWindow(), std::nullopt);
annotator_controller()->EnableAnnotatorTool();
EXPECT_TRUE(annotator_controller()->is_annotator_enabled());
EXPECT_CALL(observer(), OnAnnotatorStateChanged(false));
EXPECT_CALL(client_, Clear());
annotator_controller()->DisableAnnotator();
EXPECT_FALSE(annotator_controller()->is_annotator_enabled());
histogram_tester.ExpectUniqueSample(kProjectorToolbarHistogramName,
ProjectorToolbar::kMarkerTool,
1);
}
TEST_F(AnnotatorControllerTest, ToggleMarkerWithKeyboardShortcut) {
annotator_controller()->RegisterView(Shell::GetPrimaryRootWindow());
annotator_controller()->CreateAnnotationOverlayForWindow(
Shell::GetPrimaryRootWindow(), std::nullopt);
annotator_controller()->OnCanvasInitialized(true);
auto* event_generator = GetEventGenerator();
event_generator->PressAndReleaseKey(ui::VKEY_OEM_3, ui::EF_COMMAND_DOWN);
EXPECT_TRUE(annotator_controller()->is_annotator_enabled());
event_generator->PressAndReleaseKey(ui::VKEY_OEM_3, ui::EF_COMMAND_DOWN);
EXPECT_FALSE(annotator_controller()->is_annotator_enabled());
}
TEST_F(AnnotatorControllerTest, SetAnnotatorToolAndStorePref) {
auto* annotation_tray = Shell::GetPrimaryRootWindowController()
->GetStatusAreaWidget()
->annotation_tray();
PrefService* pref_service =
Shell::Get()->session_controller()->GetActivePrefService();
EXPECT_EQ(
pref_service->GetUint64(prefs::kProjectorAnnotatorLastUsedMarkerColor),
0u);
annotator_controller()->RegisterView(Shell::GetPrimaryRootWindow());
annotator_controller()->CreateAnnotationOverlayForWindow(
Shell::GetPrimaryRootWindow(), std::nullopt);
annotator_controller()->OnCanvasInitialized(true);
ASSERT_EQ(annotation_tray->tray_container()->children().size(), 1u);
const views::ImageView* image_view = static_cast<views::ImageView*>(
annotation_tray->tray_container()->children()[0]);
EXPECT_FALSE(image_view->GetImageModel().IsEmpty());
LeftClickOn(annotation_tray);
EXPECT_TRUE(annotator_controller()->is_annotator_enabled());
annotator_controller()->DisableAnnotator();
EXPECT_FALSE(annotator_controller()->is_annotator_enabled());
EXPECT_EQ(
pref_service->GetUint64(prefs::kProjectorAnnotatorLastUsedMarkerColor),
kAnnotatorDefaultPenColor);
}
TEST_F(AnnotatorControllerTest, LongPressShowsBubble) {
annotator_controller()->RegisterView(Shell::GetPrimaryRootWindow());
annotator_controller()->CreateAnnotationOverlayForWindow(
Shell::GetPrimaryRootWindow(), std::nullopt);
annotator_controller()->OnCanvasInitialized(true);
auto* annotation_tray = Shell::GetPrimaryRootWindowController()
->GetStatusAreaWidget()
->annotation_tray();
gfx::Point location = annotation_tray->GetBoundsInScreen().CenterPoint();
ui::GestureConfiguration* gesture_config =
ui::GestureConfiguration::GetInstance();
const int old_long_press_time_in_ms = gesture_config->long_press_time_in_ms();
const base::TimeDelta old_short_press_time =
gesture_config->short_press_time();
const int old_show_press_delay_in_ms =
gesture_config->show_press_delay_in_ms();
gesture_config->set_long_press_time_in_ms(1);
gesture_config->set_short_press_time(base::Milliseconds(1));
gesture_config->set_show_press_delay_in_ms(1);
ui::test::EventGenerator* event_generator = GetEventGenerator();
event_generator->set_current_screen_location(location);
event_generator->PressTouch();
base::RunLoop run_loop;
base::SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask(
FROM_HERE, run_loop.QuitClosure(), base::Milliseconds(2));
run_loop.Run();
gesture_config->set_long_press_time_in_ms(old_long_press_time_in_ms);
gesture_config->set_short_press_time(old_short_press_time);
gesture_config->set_show_press_delay_in_ms(old_show_press_delay_in_ms);
event_generator->ReleaseTouch();
EXPECT_TRUE(annotation_tray->GetBubbleWidget());
{
ui::AXNodeData node_data;
annotation_tray->GetBubbleView()
->GetViewAccessibility()
.GetAccessibleNodeData(&node_data);
EXPECT_EQ(node_data.GetString16Attribute(ax::mojom::StringAttribute::kName),
annotation_tray->GetAccessibleNameForBubble());
}
}
TEST_F(AnnotatorControllerTest, TapEnabledAnnotation) {
annotator_controller()->RegisterView(Shell::GetPrimaryRootWindow());
annotator_controller()->CreateAnnotationOverlayForWindow(
Shell::GetPrimaryRootWindow(), std::nullopt);
annotator_controller()->OnCanvasInitialized(true);
GestureTapOn(Shell::GetPrimaryRootWindowController()
->GetStatusAreaWidget()
->annotation_tray());
EXPECT_TRUE(annotator_controller()->is_annotator_enabled());
}
TEST_F(AnnotatorControllerTest, OnCanvasInitialized) {
auto* annotation_tray = Shell::GetPrimaryRootWindowController()
->GetStatusAreaWidget()
->annotation_tray();
annotator_controller()->RegisterView(Shell::GetPrimaryRootWindow());
EXPECT_FALSE(annotation_tray->GetEnabled());
annotator_controller()->CreateAnnotationOverlayForWindow(
Shell::GetPrimaryRootWindow(), std::nullopt);
annotator_controller()->OnCanvasInitialized(true);
EXPECT_TRUE(annotator_controller()->GetAnnotatorAvailability());
EXPECT_TRUE(annotation_tray->GetEnabled());
annotator_controller()->OnCanvasInitialized(false);
EXPECT_FALSE(annotator_controller()->GetAnnotatorAvailability());
EXPECT_FALSE(annotation_tray->GetEnabled());
}
TEST_F(AnnotatorControllerTest, ToggleAnnotationTray) {
annotator_controller()->RegisterView(Shell::GetPrimaryRootWindow());
annotator_controller()->CreateAnnotationOverlayForWindow(
Shell::GetPrimaryRootWindow(), std::nullopt);
annotator_controller()->OnCanvasInitialized(true);
EXPECT_FALSE(annotator_controller()->is_annotator_enabled());
annotator_controller()->ToggleAnnotationTray();
EXPECT_TRUE(annotator_controller()->is_annotator_enabled());
}
TEST_F(AnnotatorControllerTest, CreateAnnotationsOverlayView) {
auto overlay = annotator_controller()->CreateAnnotationsOverlayView();
EXPECT_TRUE(overlay.get());
}
TEST_F(AnnotatorControllerTest, UpdateRootViewMultipleDisplays) {
UpdateDisplay("800x700,801+0-800x700");
aura::Window::Windows roots = Shell::GetAllRootWindows();
ASSERT_EQ(2u, roots.size());
auto* primary_display_tray = Shell::GetPrimaryRootWindowController()
->GetStatusAreaWidget()
->annotation_tray();
auto* external_display_tray = RootWindowController::ForWindow(roots[1])
->GetStatusAreaWidget()
->annotation_tray();
annotator_controller()->RegisterView(Shell::GetPrimaryRootWindow());
EXPECT_TRUE(primary_display_tray->visible_preferred());
EXPECT_FALSE(external_display_tray->visible_preferred());
annotator_controller()->UpdateRootView(roots[1]);
EXPECT_FALSE(primary_display_tray->visible_preferred());
EXPECT_TRUE(external_display_tray->visible_preferred());
annotator_controller()->DisableAnnotator();
EXPECT_FALSE(primary_display_tray->visible_preferred());
EXPECT_FALSE(external_display_tray->visible_preferred());
}
TEST_F(AnnotatorControllerTest, RegisterViewTwice) {
UpdateDisplay("800x700,801+0-800x700");
aura::Window::Windows roots = Shell::GetAllRootWindows();
ASSERT_EQ(2u, roots.size());
auto* primary_display_tray = Shell::GetPrimaryRootWindowController()
->GetStatusAreaWidget()
->annotation_tray();
auto* external_display_tray = RootWindowController::ForWindow(roots[1])
->GetStatusAreaWidget()
->annotation_tray();
annotator_controller()->RegisterView(Shell::GetPrimaryRootWindow());
annotator_controller()->RegisterView(roots[1]);
EXPECT_FALSE(primary_display_tray->visible_preferred());
EXPECT_TRUE(external_display_tray->visible_preferred());
annotator_controller()->DisableAnnotator();
EXPECT_FALSE(primary_display_tray->visible_preferred());
EXPECT_FALSE(external_display_tray->visible_preferred());
}
TEST_F(AnnotatorControllerTest, AnnotatorTrayAccessibleName) {
auto* annotation_tray = Shell::GetPrimaryRootWindowController()
->GetStatusAreaWidget()
->annotation_tray();
annotator_controller()->RegisterView(Shell::GetPrimaryRootWindow());
annotator_controller()->CreateAnnotationOverlayForWindow(
Shell::GetPrimaryRootWindow(), std::nullopt);
EXPECT_TRUE(annotation_tray->visible_preferred());
{
ui::AXNodeData tray_data;
annotation_tray->GetViewAccessibility().GetAccessibleNodeData(&tray_data);
EXPECT_EQ(
tray_data.GetString16Attribute(ax::mojom::StringAttribute::kName),
l10n_util::GetStringFUTF16(
IDS_ASH_STATUS_AREA_PROJECTOR_ANNOTATION_TRAY_ACCESSIBLE_TITLE,
l10n_util::GetStringUTF16(
IDS_ASH_STATUS_AREA_PROJECTOR_ANNOTATION_TRAY_OFF_STATE)));
}
annotator_controller()->EnableAnnotatorTool();
EXPECT_TRUE(annotator_controller()->is_annotator_enabled());
{
ui::AXNodeData tray_data;
annotation_tray->GetViewAccessibility().GetAccessibleNodeData(&tray_data);
EXPECT_EQ(
tray_data.GetString16Attribute(ax::mojom::StringAttribute::kName),
l10n_util::GetStringFUTF16(
IDS_ASH_STATUS_AREA_PROJECTOR_ANNOTATION_TRAY_ACCESSIBLE_TITLE,
l10n_util::GetStringUTF16(
IDS_ASH_STATUS_AREA_PROJECTOR_ANNOTATION_TRAY_ON_STATE)));
}
annotator_controller()->ResetTools();
EXPECT_FALSE(annotator_controller()->is_annotator_enabled());
{
ui::AXNodeData tray_data;
annotation_tray->GetViewAccessibility().GetAccessibleNodeData(&tray_data);
EXPECT_EQ(
tray_data.GetString16Attribute(ax::mojom::StringAttribute::kName),
l10n_util::GetStringFUTF16(
IDS_ASH_STATUS_AREA_PROJECTOR_ANNOTATION_TRAY_ACCESSIBLE_TITLE,
l10n_util::GetStringUTF16(
IDS_ASH_STATUS_AREA_PROJECTOR_ANNOTATION_TRAY_OFF_STATE)));
}
}
TEST_F(AnnotatorControllerTest, AnnotatorNotEnabledWhenNoView) {
annotator_controller()->EnableAnnotatorTool();
EXPECT_FALSE(annotator_controller()->is_annotator_enabled());
auto* annotation_tray = Shell::GetPrimaryRootWindowController()
->GetStatusAreaWidget()
->annotation_tray();
annotator_controller()->RegisterView(Shell::GetPrimaryRootWindow());
annotator_controller()->CreateAnnotationOverlayForMarkerMode(
Shell::GetPrimaryRootWindow());
EXPECT_FALSE(annotator_controller()->is_annotator_enabled());
EXPECT_FALSE(annotation_tray->GetEnabled());
EXPECT_TRUE(annotation_tray->visible_preferred());
annotator_controller()->EnableAnnotatorTool();
EXPECT_TRUE(annotator_controller()->is_annotator_enabled());
EXPECT_FALSE(annotation_tray->GetEnabled());
EXPECT_TRUE(annotation_tray->visible_preferred());
}
TEST_F(AnnotatorControllerTest, VerifyMarkerMode) {
auto* annotation_tray = Shell::GetPrimaryRootWindowController()
->GetStatusAreaWidget()
->annotation_tray();
annotator_controller()->RegisterView(Shell::GetPrimaryRootWindow());
annotator_controller()->CreateAnnotationOverlayForMarkerMode(
Shell::GetPrimaryRootWindow());
EXPECT_FALSE(annotation_tray->GetEnabled());
EXPECT_TRUE(annotation_tray->visible_preferred());
annotator_controller()->OnCanvasInitialized(true);
annotator_controller()->EnableAnnotatorTool();
EXPECT_TRUE(annotator_controller()->is_annotator_enabled());
EXPECT_TRUE(annotation_tray->GetEnabled());
annotator_controller()->DisableAnnotator();
EXPECT_FALSE(annotation_tray->GetEnabled());
EXPECT_FALSE(annotation_tray->visible_preferred());
}
}