chromium/ash/wm/splitview/split_view_metrics_controller_unittest.cc

// Copyright 2023 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/wm/splitview/split_view_metrics_controller.h"

#include "ash/constants/ash_pref_names.h"
#include "ash/session/session_controller_impl.h"
#include "ash/shell.h"
#include "ash/test/ash_test_base.h"
#include "ash/wm/desks/desk.h"
#include "ash/wm/desks/desks_controller.h"
#include "ash/wm/splitview/split_view_controller.h"
#include "ash/wm/splitview/split_view_utils.h"
#include "ash/wm/wm_event.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/time/time.h"

namespace ash {

class SplitViewMetricsControllerTest : public AshTestBase {
 public:
  SplitViewMetricsControllerTest()
      : AshTestBase(base::test::TaskEnvironment::TimeSource::MOCK_TIME) {}

  void SetUp() override {
    AshTestBase::SetUp();
    window1_ = CreateAppWindow();
    window2_ = CreateAppWindow();
    window3_ = CreateAppWindow();
    window4_ = CreateAppWindow();
  }

  void TearDown() override {
    window1_.reset();
    window2_.reset();
    window3_.reset();
    window4_.reset();
    AshTestBase::TearDown();
  }

  void AdvanceClock(base::TimeDelta delta) {
    task_environment()->AdvanceClock(delta);
    task_environment()->RunUntilIdle();
  }

 protected:
  std::unique_ptr<aura::Window> window1_;
  std::unique_ptr<aura::Window> window2_;
  std::unique_ptr<aura::Window> window3_;
  std::unique_ptr<aura::Window> window4_;

  base::HistogramTester histogram_tester_;
};

// Tests that the metrics for recording the duration between one window getting
// snapped and another window getting snapped on the other side work correctly.
TEST_F(SplitViewMetricsControllerTest, RecordSnapTwoWindowsDuration) {
  auto* desks_controller = DesksController::Get();
  desks_controller->NewDesk(DesksCreationRemovalSource::kKeyboard);

  // Snap `window1_` to the left, wait 1 minute, then snap `window1_` to the
  // right. Test it doesn't record since it's the same window.
  const WindowSnapWMEvent snap_left(WM_EVENT_SNAP_PRIMARY);
  WindowState* window_state1 = WindowState::Get(window1_.get());
  window_state1->OnWMEvent(&snap_left);
  const WindowSnapWMEvent snap_right(WM_EVENT_SNAP_SECONDARY);
  window_state1->OnWMEvent(&snap_right);
  AdvanceClock(base::Minutes(1));
  histogram_tester_.ExpectTotalCount(kSnapTwoWindowsDurationHistogramName, 0);

  // Snap `window1_` to the left, wait 30 seconds, then snap `window2_` to the
  // right. Test that it records in the 0 minute bucket.
  window_state1->OnWMEvent(&snap_left);
  AdvanceClock(base::Seconds(30));
  WindowState* window_state2 = WindowState::Get(window2_.get());
  window_state2->OnWMEvent(&snap_right);
  histogram_tester_.ExpectTimeBucketCount(kSnapTwoWindowsDurationHistogramName,
                                          base::Seconds(30), 1);

  // Snap `window2_` to the left, wait 3 minutes, then snap `window1_` to the
  // right. Test that it records in the 3 minute bucket.
  window_state2->OnWMEvent(&snap_left);
  AdvanceClock(base::Minutes(3));
  window_state1->OnWMEvent(&snap_right);
  histogram_tester_.ExpectTimeBucketCount(kSnapTwoWindowsDurationHistogramName,
                                          base::Minutes(3), 1);

  // Snap `window1_` to the left, wait 3 minutes, open a new `window3_` and
  // close it to simulate real user sessions with multiple windows, then snap
  // `window2_` to the right. Test that it increments the 3 minute bucket.
  window_state1->OnWMEvent(&snap_left);
  AdvanceClock(base::Minutes(3));
  window3_.reset();
  window_state2->OnWMEvent(&snap_right);
  histogram_tester_.ExpectTimeBucketCount(kSnapTwoWindowsDurationHistogramName,
                                          base::Minutes(3), 2);

  // Snap `window1_` to the right, wait 3 minutes, then minimize it. Test that
  // it records in the max bucket, since no other window was snapped.
  window_state1->OnWMEvent(&snap_right);
  AdvanceClock(base::Minutes(3));
  window_state1->Minimize();
  histogram_tester_.ExpectTimeBucketCount(kSnapTwoWindowsDurationHistogramName,
                                          kSequentialSnapActionMaxTime, 1);

  // Snap a new `window4_` to the left, wait 3 minutes, then close it. Test that
  // it records in the max bucket, since no other window was snapped.
  WindowState::Get(window4_.get())->OnWMEvent(&snap_left);
  AdvanceClock(base::Minutes(3));
  window4_.reset();
  histogram_tester_.ExpectTimeBucketCount(kSnapTwoWindowsDurationHistogramName,
                                          kSequentialSnapActionMaxTime, 2);

  // Snap `window1_` to the left, wait 3 minutes, move it to a new desk, move
  // `window2_` to the same desk. Test it doesn't record anything.
  window_state1->OnWMEvent(&snap_left);
  AdvanceClock(base::Minutes(3));
  desks_controller->NewDesk(DesksCreationRemovalSource::kKeyboard);
  desks_controller->desks()[0]->MoveWindowToDesk(
      window1_.get(), desks_controller->desks()[1].get(),
      window1_->GetRootWindow(), /*unminimize=*/true);
  desks_controller->desks()[0]->MoveWindowToDesk(
      window2_.get(), desks_controller->desks()[1].get(),
      window2_->GetRootWindow(), /*unminimize=*/true);
  histogram_tester_.ExpectTotalCount(kSnapTwoWindowsDurationHistogramName, 5);
}

// Tests the metrics for the elapsed time between the first snapped window
// getting minimized and the second snapped window getting minimized.
TEST_F(SplitViewMetricsControllerTest, RecordMinimizeTwoWindowsDuration) {
  // Snap `window1_` and `window2_`.
  const WindowSnapWMEvent snap_left(WM_EVENT_SNAP_PRIMARY);
  WindowState* window_state1 = WindowState::Get(window1_.get());
  window_state1->OnWMEvent(&snap_left);
  const WindowSnapWMEvent snap_right(WM_EVENT_SNAP_SECONDARY);
  WindowState* window_state2 = WindowState::Get(window2_.get());
  window_state2->OnWMEvent(&snap_right);
  histogram_tester_.ExpectTotalCount(kSnapTwoWindowsDurationHistogramName, 1);

  // Minimize `window1_`, wait 3 minutes, then minimize `window2_`.
  window_state1->Minimize();
  AdvanceClock(base::Minutes(3));
  window_state2->Minimize();
  histogram_tester_.ExpectTimeBucketCount(
      kMinimizeTwoWindowsDurationHistogramName, base::Minutes(3), 1);

  // Minimize `window1_`, wait 1 minute, then maximize `window1_`. Test it
  // records in the maximum bucket.
  window_state1->Restore();
  window_state2->Restore();
  EXPECT_TRUE(window_state1->IsSnapped());
  EXPECT_TRUE(window_state2->IsSnapped());
  window_state1->Minimize();
  AdvanceClock(base::Minutes(3));
  window_state1->Maximize();
  histogram_tester_.ExpectTimeBucketCount(
      kMinimizeTwoWindowsDurationHistogramName, kSequentialSnapActionMaxTime,
      1);

  // Minimize `window1_`, wait 2 minutes, restore it to snapped state, then
  // minimize it again. Test we don't record anything.
  window_state1->OnWMEvent(&snap_left);
  window_state1->Minimize();
  AdvanceClock(base::Minutes(3));
  window_state1->Restore();
  EXPECT_TRUE(window_state1->IsSnapped());
  window_state1->Minimize();
  histogram_tester_.ExpectTotalCount(kMinimizeTwoWindowsDurationHistogramName,
                                     3);

  // Minimize `window2_`, wait 1 minute, then close `window2_`. Test it records
  // in the maximum bucket.
  window_state1->OnWMEvent(&snap_left);
  window_state2->OnWMEvent(&snap_right);
  EXPECT_TRUE(window_state1->IsSnapped());
  EXPECT_TRUE(window_state2->IsSnapped());
  window_state2->Minimize();
  AdvanceClock(base::Minutes(3));
  window2_.reset();
  histogram_tester_.ExpectTimeBucketCount(
      kMinimizeTwoWindowsDurationHistogramName, kSequentialSnapActionMaxTime,
      2);
  histogram_tester_.ExpectTotalCount(kMinimizeTwoWindowsDurationHistogramName,
                                     4);
}

// Tests that the metrics for recording the duration between closing a snapped
// window and closing another snapped window on the opposite side work
// correctly.
TEST_F(SplitViewMetricsControllerTest, CloseSnapTwoWindowsDuration) {
  // Snap `window1_` and `window2_`.
  const WindowSnapWMEvent snap_left(WM_EVENT_SNAP_PRIMARY);
  WindowState* window_state1 = WindowState::Get(window1_.get());
  window_state1->OnWMEvent(&snap_left);
  WindowState* window_state2 = WindowState::Get(window2_.get());
  const WindowSnapWMEvent snap_right(WM_EVENT_SNAP_SECONDARY);
  window_state2->OnWMEvent(&snap_right);

  // Close `window1_`, wait 3 minutes, then close `window2_`. Test it records in
  // the 3 minute bucket.
  window1_.reset();
  AdvanceClock(base::Minutes(3));
  window2_.reset();
  histogram_tester_.ExpectTimeBucketCount(kCloseTwoWindowsDurationHistogramName,
                                          base::Minutes(3), 1);

  // Snap `window3_` and `window4_`.
  WindowState* window_state3 = WindowState::Get(window3_.get());
  WindowState* window_state4 = WindowState::Get(window4_.get());
  window_state3->OnWMEvent(&snap_left);
  window_state4->OnWMEvent(&snap_right);

  // Close `window3_`, wait 5 minutes, then maximize `window4_`. Test it records
  // in the max bucket.
  window3_.reset();
  AdvanceClock(base::Minutes(5));
  window_state4->Maximize();
  histogram_tester_.ExpectTimeBucketCount(kCloseTwoWindowsDurationHistogramName,
                                          kSequentialSnapActionMaxTime, 1);
}

// Tests that snapping then toggling float between 2 windows doesn't crash
// (b/314823042).
TEST_F(SplitViewMetricsControllerTest, FloatToSnap) {
  // Float `window1`.
  WindowState* window_state1 = WindowState::Get(window1_.get());
  const WindowFloatWMEvent float_event(
      chromeos::FloatStartLocation::kBottomRight);
  window_state1->OnWMEvent(&float_event);

  // Snap then float `window2`.
  WindowState* window_state2 = WindowState::Get(window2_.get());
  const WindowSnapWMEvent snap_right(WM_EVENT_SNAP_SECONDARY,
                                     chromeos::kOneThirdSnapRatio);
  window_state2->OnWMEvent(&snap_right);
  window_state2->OnWMEvent(&float_event);

  // Snap then float `window1`.
  window_state1->OnWMEvent(&snap_right);
  window_state1->OnWMEvent(&float_event);
}

// Tests that we record the pref value whenever a window is snapped.
TEST_F(SplitViewMetricsControllerTest, SnapWindowSuggestions) {
  // The pref is enabled by default.
  PrefService* pref =
      Shell::Get()->session_controller()->GetActivePrefService();
  ASSERT_TRUE(pref->GetBoolean(prefs::kSnapWindowSuggestions));

  const auto snap_action_sources = {
      WindowSnapActionSource::kSnapByWindowLayoutMenu,
      WindowSnapActionSource::kDragWindowToEdgeToSnap,
      WindowSnapActionSource::kLongPressCaptionButtonToSnap,
      WindowSnapActionSource::kLacrosSnapButtonOrWindowLayoutMenu,
      WindowSnapActionSource::kKeyboardShortcutToSnap,
      WindowSnapActionSource::kSnapByWindowStateRestore,
      WindowSnapActionSource::kSnapByFullRestoreOrDeskTemplateOrSavedDesk,
  };
  for (const auto snap_action_source : snap_action_sources) {
    // Verify initial histogram values.
    std::string histogram_name(
        BuildSnapWindowSuggestionsHistogramName(snap_action_source));
    histogram_tester_.ExpectTotalCount(histogram_name,
                                       /*expected_count=*/0);

    // We only increment the histogram if the source can start split view.
    bool increment =
        CanSnapActionSourceStartFasterSplitView(snap_action_source);

    // Enable the pref. Test it increments the `true` bucket.
    pref->SetBoolean(prefs::kSnapWindowSuggestions, true);
    const WindowSnapWMEvent snap_left(WM_EVENT_SNAP_PRIMARY,
                                      snap_action_source);
    WindowState* window_state1 = WindowState::Get(window1_.get());
    window_state1->OnWMEvent(&snap_left);
    histogram_tester_.ExpectBucketCount(histogram_name,
                                        /*sample=*/true,
                                        /*expected_count=*/increment ? 1 : 0);

    // Disable the pref. Test it increments the `false` bucket.
    pref->SetBoolean(prefs::kSnapWindowSuggestions, false);
    const WindowSnapWMEvent snap_right(WM_EVENT_SNAP_SECONDARY,
                                       snap_action_source);
    window_state1->OnWMEvent(&snap_right);
    histogram_tester_.ExpectBucketCount(histogram_name,
                                        /*sample=*/false,
                                        /*expected_count=*/increment ? 1 : 0);
  }
}

}  // namespace ash