910e62b5创建于 1月15日历史提交
// 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_features.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 "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) {
    // Explicitly enable Sunfish so the Home Button has a long-press action.
    scoped_feature_list_.InitAndEnableFeature(features::kSunfishFeature);
  }

  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();

    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());

    // Allow Sunfish via enterprise policy.
    // Previously this was set to 'false', disabling the only remaining
    // long-press action.
    test_capture_mode_delegate->set_is_search_allowed_by_policy(true);

    // Scanner can remain disabled if Sunfish is enough to trigger the
    // animation.
    prefs->SetInteger(prefs::kScannerEnterprisePolicyAllowed,
                      static_cast<int>(ScannerEnterprisePolicy::kDisallowed));

    ASSERT_TRUE(CanShowSunfishOrScannerUi());

    const TestVariant test_variant = GetParam();
    switch (test_variant) {
      case kClamshell:
        ASSERT_FALSE(display::Screen::Get()->InTabletMode());
        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_;
  base::test::ScopedFeatureList scoped_feature_list_;
};

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