blob: dab362a988a004afbe8d8bd5442e2cc7635cb130 [file] [log] [blame]
// Copyright 2021 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "ash/shelf/home_button_tap_overlay.h"
#include <memory>
#include <string>
#include <vector>
#include "ash/capture_mode/capture_mode_controller.h"
#include "ash/capture_mode/test_capture_mode_delegate.h"
#include "ash/constants/ash_pref_names.h"
#include "ash/constants/ash_switches.h"
#include "ash/public/cpp/capture_mode/capture_mode_api.h"
#include "ash/public/cpp/shelf_config.h"
#include "ash/scanner/scanner_enterprise_policy.h"
#include "ash/session/session_controller_impl.h"
#include "ash/shelf/home_button.h"
#include "ash/shelf/shelf_navigation_widget.h"
#include "ash/shelf/shelf_view.h"
#include "ash/shelf/shelf_view_test_api.h"
#include "ash/shelf/shelf_widget.h"
#include "ash/shell.h"
#include "ash/test/ash_test_base.h"
#include "ash/wm/tablet_mode/tablet_mode_controller.h"
#include "base/command_line.h"
#include "base/memory/ptr_util.h"
#include "base/test/scoped_feature_list.h"
#include "base/test/task_environment.h"
#include "base/time/time.h"
#include "chromeos/ash/services/assistant/public/cpp/features.h"
#include "testing/gmock/include/gmock/gmock-matchers.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest-param-test.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/compositor/layer.h"
#include "ui/events/event.h"
#include "ui/events/event_constants.h"
#include "ui/events/gesture_event_details.h"
#include "ui/events/types/event_type.h"
#include "ui/gfx/geometry/point_conversions.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/views/view.h"
#include "ui/wm/core/window_util.h"
namespace ash {
namespace {
constexpr base::TimeDelta kAnimationDelay = base::Milliseconds(200);
constexpr char kOverlayClassName[] = "HomeButtonTapOverlay";
enum TestVariant { kClamshell, kTablet, kTabletWithBackButton };
class HomeButtonTapOverlayTest
: public AshTestBase,
public testing::WithParamInterface<TestVariant> {
public:
HomeButtonTapOverlayTest()
: AshTestBase(base::test::TaskEnvironment::TimeSource::MOCK_TIME) {}
void SetUp() override {
// In Tablet mode, home button is shown if a board has kAshEnableTabletMode
// and kAccessibilityTabletModeShelfNavigationButtonsEnabled is true.
base::CommandLine::ForCurrentProcess()->AppendSwitch(
switches::kAshEnableTabletMode);
AshTestBase::SetUp();
if (ash::assistant::features::IsNewEntryPointEnabled()) {
GTEST_SKIP()
<< "Assistant is not available if new entry point is enabled. "
"crbug.com/388361414";
}
PrefService* prefs =
Shell::Get()->session_controller()->GetActivePrefService();
// Disable Sunfish and Scanner via enterprise policies.
auto* capture_mode_controller = CaptureModeController::Get();
auto* test_capture_mode_delegate = static_cast<TestCaptureModeDelegate*>(
capture_mode_controller->delegate_for_testing());
test_capture_mode_delegate->set_is_search_allowed_by_policy(false);
prefs->SetInteger(prefs::kScannerEnterprisePolicyAllowed,
static_cast<int>(ScannerEnterprisePolicy::kDisallowed));
ASSERT_FALSE(CanShowSunfishOrScannerUi());
const TestVariant test_variant = GetParam();
switch (test_variant) {
case kClamshell:
ASSERT_FALSE(Shell::Get()->IsInTabletMode());
break;
case kTablet:
Shell::Get()->session_controller()->GetActivePrefService()->SetBoolean(
prefs::kAccessibilityTabletModeShelfNavigationButtonsEnabled, true);
Shell::Get()->tablet_mode_controller()->SetEnabledForTest(true);
ASSERT_FALSE(ShelfConfig::Get()->is_in_app());
break;
case kTabletWithBackButton:
Shell::Get()->session_controller()->GetActivePrefService()->SetBoolean(
prefs::kAccessibilityTabletModeShelfNavigationButtonsEnabled, true);
Shell::Get()->tablet_mode_controller()->SetEnabledForTest(true);
// A back button is shown if there is an active window.
CreateTestWindow();
ActivateTestWindow();
ASSERT_TRUE(ShelfConfig::Get()->is_in_app());
break;
}
}
void TearDown() override {
window_.reset();
AshTestBase::TearDown();
}
protected:
const views::View* GetTapOverlay() {
for (const views::View* child : home_button()->children()) {
if (child->GetClassName() == kOverlayClassName) {
return child;
}
}
return nullptr;
}
HomeButton* home_button() {
return GetPrimaryShelf()
->shelf_widget()
->navigation_widget()
->GetHomeButton();
}
std::vector<gfx::Rect> TakeClipRectSnapshot(const ui::Layer* layer) {
std::vector<gfx::Rect> clip_rects;
while (layer) {
clip_rects.push_back(layer->clip_rect());
layer = layer->parent();
}
return clip_rects;
}
void SendGestureEventToHomeButton(ui::EventType type) {
ui::GestureEvent event(0, 0, ui::EF_NONE, base::TimeTicks(),
ui::GestureEventDetails(type));
home_button()->OnGestureEvent(&event);
}
void ActivateTestWindow() { wm::ActivateWindow(window_.get()); }
private:
void CreateTestWindow() {
window_ = AshTestBase::CreateTestWindow({400, 400});
}
std::unique_ptr<aura::Window> window_;
};
INSTANTIATE_TEST_SUITE_P(All,
HomeButtonTapOverlayTest,
testing::Values(TestVariant::kClamshell,
TestVariant::kTablet,
TestVariant::kTabletWithBackButton));
// HomeButtonTapOverlay renders burst animation which goes outside of its view
// size. Make sure that it's not clipped by an ancestor layer.
//
// For long press, events and expectations are follows:
// - EventType::kGestureTapDown: Ripple animation starts
// - EventType::kGestureLongPress: Burst animation starts (clip rect will be
// restored
// after the animation)
// - EventType::kGestureTapCancel: Nothing should happen
// - EventType::kGestureLongTap: Nothing should happen
TEST_P(HomeButtonTapOverlayTest, BurstAnimationWithLongPress) {
const views::View* tap_overlay = GetTapOverlay();
ASSERT_THAT(tap_overlay, testing::NotNull());
const ui::Layer* tap_overlay_layer = tap_overlay->layer();
std::vector<gfx::Rect> clip_rect_snapshot_before =
TakeClipRectSnapshot(tap_overlay_layer);
SendGestureEventToHomeButton(ui::EventType::kGestureTapDown);
// HomeButtonController delays the animation for kAnimationDelay.
task_environment()->FastForwardBy(kAnimationDelay);
// Confirm that no clip rect is set in ancestor layers.
std::vector<gfx::Rect> clip_rects_during_animation =
TakeClipRectSnapshot(tap_overlay_layer);
for (const gfx::Rect& clip_rect : clip_rects_during_animation) {
EXPECT_TRUE(clip_rect.IsEmpty());
}
// HomeButtonTapOverlay starts burst animation with
// EventType::kGestureLongPress.
SendGestureEventToHomeButton(ui::EventType::kGestureLongPress);
// Burst animation ends immediately in this test case. Confirm that clip rect
// is restored now.
EXPECT_EQ(TakeClipRectSnapshot(tap_overlay_layer), clip_rect_snapshot_before);
SendGestureEventToHomeButton(ui::EventType::kGestureTapCancel);
SendGestureEventToHomeButton(ui::EventType::kGestureLongTap);
// Confirm that nothing should happen with the following TAP_CANCEL and
// LONG_TAP events.
EXPECT_EQ(TakeClipRectSnapshot(tap_overlay_layer), clip_rect_snapshot_before);
}
// HomeButtonTapOverlay renders a ripple animation with a tap, which goes beyond
// the size of home button.
TEST_P(HomeButtonTapOverlayTest, RippleAnimationWithTap) {
const views::View* tap_overlay = GetTapOverlay();
ASSERT_THAT(tap_overlay, testing::NotNull());
const ui::Layer* tap_overlay_layer = tap_overlay->layer();
std::vector<gfx::Rect> clip_rect_snapshot_before =
TakeClipRectSnapshot(tap_overlay_layer);
SendGestureEventToHomeButton(ui::EventType::kGestureTapDown);
// HomeButtonController delays assistant animation for
// kAssistantAnimationDelay.
task_environment()->FastForwardBy(kAnimationDelay);
// Confirm that no clip rect is set in ancestor layers.
std::vector<gfx::Rect> clip_rects_during_animation =
TakeClipRectSnapshot(tap_overlay_layer);
for (const gfx::Rect& clip_rect : clip_rects_during_animation) {
EXPECT_TRUE(clip_rect.IsEmpty());
}
SendGestureEventToHomeButton(ui::EventType::kGestureTap);
// The above tap will de-activate test window and hides back button.
// Re-activate the window to show a back button. Clip rect can be different if
// no back button is shown.
if (GetParam() == kTabletWithBackButton) {
ActivateTestWindow();
}
EXPECT_EQ(TakeClipRectSnapshot(tap_overlay_layer), clip_rect_snapshot_before);
}
// HomeButtonTapOverlay renders a ripple animation with a tap, which goes beyond
// the size of home button.
TEST_P(HomeButtonTapOverlayTest,
RippleAnimationWithAssistantDisabledDuringTap) {
const views::View* tap_overlay = GetTapOverlay();
ASSERT_THAT(tap_overlay, testing::NotNull());
const ui::Layer* tap_overlay_layer = tap_overlay->layer();
std::vector<gfx::Rect> clip_rect_snapshot_before =
TakeClipRectSnapshot(tap_overlay_layer);
SendGestureEventToHomeButton(ui::EventType::kGestureTapDown);
// HomeButtonController delays assistant animation for
// kAssistantAnimationDelay.
task_environment()->FastForwardBy(kAnimationDelay);
// Confirm that no clip rect is set in ancestor layers.
std::vector<gfx::Rect> clip_rects_during_animation =
TakeClipRectSnapshot(tap_overlay_layer);
for (const gfx::Rect& clip_rect : clip_rects_during_animation) {
EXPECT_TRUE(clip_rect.IsEmpty());
}
SendGestureEventToHomeButton(ui::EventType::kGestureTap);
// The above tap will de-activate test window and hides back button.
// Re-activate the window to show a back button. Clip rect can be different if
// no back button is shown.
if (GetParam() == kTabletWithBackButton) {
ActivateTestWindow();
}
EXPECT_EQ(TakeClipRectSnapshot(tap_overlay_layer), clip_rect_snapshot_before);
}
} // namespace
} // namespace ash