blob: 4797d200f32b48e43cf3ff8e62530aff38641672 [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/launcher_nudge_controller.h"
#include "ash/app_list/app_list_controller_impl.h"
#include "ash/app_list/test/app_list_test_helper.h"
#include "ash/constants/ash_features.h"
#include "ash/constants/ash_pref_names.h"
#include "ash/constants/ash_switches.h"
#include "ash/session/session_controller_impl.h"
#include "ash/shelf/home_button.h"
#include "ash/shelf/scrollable_shelf_view.h"
#include "ash/shelf/shelf_controller.h"
#include "ash/shelf/shelf_test_util.h"
#include "ash/shelf/shelf_view_test_api.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/json/values_util.h"
#include "base/test/scoped_feature_list.h"
#include "base/test/task_environment.h"
#include "ui/compositor/layer.h"
#include "ui/compositor/scoped_animation_duration_scale_mode.h"
namespace ash {
class TestNudgeAnimationObserver : public HomeButton::NudgeAnimationObserver {
public:
explicit TestNudgeAnimationObserver(HomeButton* home_button)
: home_button_(home_button) {
home_button_->AddNudgeAnimationObserverForTest(this);
}
~TestNudgeAnimationObserver() override {
home_button_->RemoveNudgeAnimationObserverForTest(this);
}
// HomeButton::NudgeAnimationObserver:
void NudgeAnimationStarted(HomeButton* home_button) override {
if (home_button != home_button_)
return;
++started_animation_count_;
}
void NudgeAnimationEnded(HomeButton* home_button) override {
if (home_button != home_button_)
return;
DCHECK_EQ(started_animation_count_, ended_animation_count_ + 1);
++ended_animation_count_;
animation_run_loop_.Quit();
}
void NudgeLabelShown(HomeButton* home_button) override {
if (home_button != home_button_)
return;
label_run_loop_.Quit();
}
void WaitUntilLabelShown() {
ASSERT_TRUE(home_button_->CanShowNudgeLabel());
DCHECK_GE(started_animation_count_, ended_animation_count_);
if (started_animation_count_ == ended_animation_count_)
return;
// Block the test to wait until the label is shown.
label_run_loop_.Run();
}
void WaitUntilAnimationEnded() {
DCHECK_GE(started_animation_count_, ended_animation_count_);
if (started_animation_count_ == ended_animation_count_)
return;
// Block the test to wait until the animation ended.
animation_run_loop_.Run();
}
// Returns the number of finished animation on this home_button_.
int GetShownCount() const { return ended_animation_count_; }
private:
base::RunLoop animation_run_loop_;
base::RunLoop label_run_loop_;
HomeButton* const home_button_;
// Counts the number of started/ended animations.
int started_animation_count_ = 0;
int ended_animation_count_ = 0;
};
class LauncherNudgeControllerTest : public AshTestBase {
public:
LauncherNudgeControllerTest()
: AshTestBase(base::test::TaskEnvironment::TimeSource::MOCK_TIME) {}
LauncherNudgeControllerTest(const LauncherNudgeControllerTest&) = delete;
LauncherNudgeControllerTest& operator=(const LauncherNudgeControllerTest&) =
delete;
~LauncherNudgeControllerTest() override = default;
void SetUp() override {
AshTestBase::SetUp();
nudge_controller_ =
Shell::Get()->shelf_controller()->launcher_nudge_controller();
nudge_controller_->SetClockForTesting(
task_environment()->GetMockClock(),
task_environment()->GetMockTickClock());
scrollable_shelf_view_ = GetPrimaryShelf()
->shelf_widget()
->hotseat_widget()
->scrollable_shelf_view();
test_api_ = std::make_unique<ShelfViewTestAPI>(
scrollable_shelf_view_->shelf_view());
}
// Advances the mock clock in the task environment and wait until it is idle.
// Note that AdvanceClock is used here instead of FastForwardBy because
// `delay` used in test cases are too long for FastForwardBy to process and
// will cause timeout running the tests.
void AdvanceClock(base::TimeDelta delay) {
task_environment()->AdvanceClock(delay);
task_environment()->RunUntilIdle();
}
int GetNudgeShownCount() {
PrefService* pref_service =
Shell::Get()->session_controller()->GetLastActiveUserPrefService();
return LauncherNudgeController::GetShownCount(pref_service);
}
void AddAppShortcut(int& id) {
ShelfItem item = ShelfTestUtil::AddAppShortcut(base::NumberToString(id++),
TYPE_PINNED_APP);
// Wait for shelf view's bounds animation to end. Otherwise the scrollable
// shelf's bounds are not updated yet.
test_api_->RunMessageLoopUntilAnimationsDone();
}
LauncherNudgeController* nudge_controller_;
std::unique_ptr<TestNudgeAnimationObserver> observer_;
ScrollableShelfView* scrollable_shelf_view_ = nullptr;
std::unique_ptr<ShelfViewTestAPI> test_api_;
};
TEST_F(LauncherNudgeControllerTest, DisableNudgeForGuestSession) {
// Simulate a guest user logging in, where the session is ephemeral.
SimulateGuestLogin();
// Do not show the nudge in the guest session.
EXPECT_FALSE(nudge_controller_->IsRecheckTimerRunningForTesting());
EXPECT_EQ(0, GetNudgeShownCount());
}
TEST_F(LauncherNudgeControllerTest, NoNudgeWhenSkippedByCommandLineFlag) {
// Unit tests run with a scoped command line, so directly set the flag.
base::CommandLine::ForCurrentProcess()->AppendSwitch(switches::kAshNoNudges);
SimulateUserLogin("user@gmail.com");
EXPECT_FALSE(nudge_controller_->IsRecheckTimerRunningForTesting());
EXPECT_EQ(0, GetNudgeShownCount());
}
TEST_F(LauncherNudgeControllerTest, DisableNudgeForExistingUser) {
// Simulate a existing user logging in.
SimulateUserLogin("user@gmail.com");
ASSERT_FALSE(Shell::Get()->session_controller()->IsUserFirstLogin());
// Do not show the nudge to an existing user.
EXPECT_FALSE(nudge_controller_->IsRecheckTimerRunningForTesting());
EXPECT_EQ(0, GetNudgeShownCount());
}
TEST_F(LauncherNudgeControllerTest, BasicTest) {
// Set the animation duration mode to non-zero for the launcher nudge
// animation to actually run in the tests.
ui::ScopedAnimationDurationScaleMode non_zero_duration_mode(
ui::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION);
SimulateNewUserFirstLogin("user@gmail.com");
ASSERT_TRUE(Shell::Get()->session_controller()->IsUserFirstLogin());
EXPECT_EQ(0, GetNudgeShownCount());
for (int i = 0; i < LauncherNudgeController::kMaxShownCount; ++i) {
// After the new user logged in or the nudge was just shown, the recheck
// timer should be running to wait for a certain amount of time to check if
// the nudge should be shown later.
EXPECT_TRUE(nudge_controller_->IsRecheckTimerRunningForTesting());
if (i == 0) {
// The first waiting interval is set different from the following ones.
AdvanceClock(nudge_controller_->GetNudgeInterval(/*is_first_time=*/true));
} else {
AdvanceClock(
nudge_controller_->GetNudgeInterval(/*is_first_time=*/false));
}
// The nudge animation should be shown and the shown count should be
// incremented.
EXPECT_EQ(i + 1, GetNudgeShownCount());
}
// After showing `kMaxShownCount` times of the nudge animation, stop showing
// nudges to the users.
EXPECT_FALSE(nudge_controller_->IsRecheckTimerRunningForTesting());
}
TEST_F(LauncherNudgeControllerTest, StopShowingNudgeAfterLauncherIsOpened) {
// Set the animation duration mode to non-zero for the launcher nudge
// animation to actually run in the tests.
ui::ScopedAnimationDurationScaleMode non_zero_duration_mode(
ui::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION);
SimulateNewUserFirstLogin("user@gmail.com");
EXPECT_EQ(0, GetNudgeShownCount());
EXPECT_TRUE(nudge_controller_->IsRecheckTimerRunningForTesting());
AdvanceClock(nudge_controller_->GetNudgeInterval(/*is_first_time=*/true));
EXPECT_EQ(1, GetNudgeShownCount());
// Toggle the app list to show.
Shell::Get()->app_list_controller()->ToggleAppList(
display::Screen::GetScreen()->GetPrimaryDisplay().id(),
AppListShowSource::kShelfButton, base::TimeTicks());
ASSERT_TRUE(Shell::Get()->app_list_controller()->IsVisible());
AdvanceClock(nudge_controller_->GetNudgeInterval(/*is_first_time=*/false));
// If the launcher app list has been opened, stop showing the nudge to the
// user.
EXPECT_FALSE(nudge_controller_->IsRecheckTimerRunningForTesting());
EXPECT_EQ(1, GetNudgeShownCount());
}
TEST_F(LauncherNudgeControllerTest, DoNotShowNudgeInTabletMode) {
// Set the animation duration mode to non-zero for the launcher nudge
// animation to actually run in the tests.
ui::ScopedAnimationDurationScaleMode non_zero_duration_mode(
ui::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION);
SimulateNewUserFirstLogin("user@gmail.com");
EXPECT_EQ(0, GetNudgeShownCount());
// Change to the tablet mode. Do not show the nudge in tablet mode.
Shell::Get()->tablet_mode_controller()->SetEnabledForTest(true);
AdvanceClock(nudge_controller_->GetNudgeInterval(/*is_first_time=*/true));
EXPECT_EQ(0, GetNudgeShownCount());
// Return to the clamshell mode.
Shell::Get()->tablet_mode_controller()->SetEnabledForTest(false);
EXPECT_EQ(0, GetNudgeShownCount());
// The nudge has to wait for `kMinIntervalAfterHomeButtonAppears` after
// changing back to clamshell mode.
AdvanceClock(LauncherNudgeController::kMinIntervalAfterHomeButtonAppears);
EXPECT_EQ(1, GetNudgeShownCount());
}
TEST_F(LauncherNudgeControllerTest, ShowNudgeOnDisplayWhereCursorIsOn) {
// Set the animation duration mode to non-zero for the launcher nudge
// animation to actually run in the tests.
ui::ScopedAnimationDurationScaleMode non_zero_duration_mode(
ui::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION);
SimulateNewUserFirstLogin("user@gmail.com");
EXPECT_EQ(0, GetNudgeShownCount());
// Create 2 displays for the test.
UpdateDisplay("800x600,800x600");
ASSERT_EQ(2u, Shell::Get()->display_manager()->GetNumDisplays());
HomeButton* primary_home_button =
LauncherNudgeController::GetHomeButtonForDisplay(
GetPrimaryDisplay().id());
HomeButton* secondary_home_button =
LauncherNudgeController::GetHomeButtonForDisplay(
GetSecondaryDisplay().id());
TestNudgeAnimationObserver waiter_primary(primary_home_button);
TestNudgeAnimationObserver waiter_secondary(secondary_home_button);
// Move the cursor to primary display. The nudge after advancing the clock
// should be shown on the primary display.
Shell::Get()->cursor_manager()->SetDisplay(GetPrimaryDisplay());
AdvanceClock(nudge_controller_->GetNudgeInterval(/*is_first_time=*/true));
// Wait until the animation ends to count the finished animation.
waiter_primary.WaitUntilAnimationEnded();
EXPECT_EQ(1, GetNudgeShownCount());
EXPECT_EQ(1, waiter_primary.GetShownCount());
EXPECT_EQ(0, waiter_secondary.GetShownCount());
// Move the cursor to primary display. The nudge after advancing the clock
// should be shown on the primary display.
Shell::Get()->cursor_manager()->SetDisplay(GetSecondaryDisplay());
AdvanceClock(nudge_controller_->GetNudgeInterval(/*is_first_time=*/false));
// Wait until the animation ends to count the finished animation.
waiter_secondary.WaitUntilAnimationEnded();
EXPECT_EQ(2, GetNudgeShownCount());
EXPECT_EQ(1, waiter_primary.GetShownCount());
EXPECT_EQ(1, waiter_secondary.GetShownCount());
}
TEST_F(LauncherNudgeControllerTest,
WaitUntilHomeButtonStaysLongEnoughToShowNudge) {
// Set the animation duration mode to non-zero for the launcher nudge
// animation to actually run in the tests.
ui::ScopedAnimationDurationScaleMode non_zero_duration_mode(
ui::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION);
// New user logs in.
SimulateNewUserFirstLogin("user@gmail.com");
EXPECT_EQ(0, GetNudgeShownCount());
base::TimeDelta small_delta = base::Seconds(10);
// Log out right before the nudge should be shown.
AdvanceClock(nudge_controller_->GetNudgeInterval(/*is_first_time=*/true) -
small_delta);
ClearLogin();
// Log in again.
SimulateUserLogin("user@gmail.com");
AdvanceClock(small_delta);
// Even if the nudge interval has passed since the first log in, the nudge has
// to wait `kMinIntervalAfterHomeButtonAppears` amount of time since the
// recent login to be shown.
EXPECT_EQ(0, GetNudgeShownCount());
AdvanceClock(LauncherNudgeController::kMinIntervalAfterHomeButtonAppears -
small_delta);
EXPECT_EQ(1, GetNudgeShownCount());
// Change to the tablet mode.
Shell::Get()->tablet_mode_controller()->SetEnabledForTest(true);
AdvanceClock(nudge_controller_->GetNudgeInterval(/*is_first_time=*/true) -
small_delta);
// Return to the clamshell mode.
Shell::Get()->tablet_mode_controller()->SetEnabledForTest(false);
AdvanceClock(small_delta);
// Even if the nudge interval has passed since the last nudge shown, the nudge
// has to wait `kMinIntervalAfterHomeButtonAppears` amount of time since the
// last change to clamshell mode to be shown.
EXPECT_EQ(1, GetNudgeShownCount());
AdvanceClock(LauncherNudgeController::kMinIntervalAfterHomeButtonAppears -
small_delta);
EXPECT_EQ(2, GetNudgeShownCount());
}
TEST_F(LauncherNudgeControllerTest, NudgeLabelVisibilityTest) {
// Set the animation duration mode to non-zero for the launcher nudge
// animation to actually run in the tests.
ui::ScopedAnimationDurationScaleMode non_zero_duration_mode(
ui::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION);
SimulateNewUserFirstLogin("user@gmail.com");
EXPECT_EQ(GetNudgeShownCount(), 0);
HomeButton* home_button = LauncherNudgeController::GetHomeButtonForDisplay(
GetPrimaryDisplay().id());
TestNudgeAnimationObserver waiter(home_button);
AdvanceClock(nudge_controller_->GetNudgeInterval(/*is_first_time=*/true));
// Wait until the label to be shown and check if the label is visible.
waiter.WaitUntilLabelShown();
views::View* label_container = home_button->label_container_for_test();
EXPECT_TRUE(label_container && label_container->GetVisible());
EXPECT_EQ(label_container->layer()->opacity(), 1);
// Wait until the label is hidden.
AdvanceClock(base::TimeDelta(base::Seconds(6)));
waiter.WaitUntilAnimationEnded();
EXPECT_FALSE(label_container->GetVisible());
EXPECT_EQ(label_container->layer()->opacity(), 0);
EXPECT_EQ(GetNudgeShownCount(), 1);
TestNudgeAnimationObserver waiter2(home_button);
AdvanceClock(nudge_controller_->GetNudgeInterval(/*is_first_time=*/false) -
base::Seconds(6));
waiter2.WaitUntilLabelShown();
EXPECT_TRUE(label_container->GetVisible());
gfx::Point center = label_container->GetBoundsInScreen().CenterPoint();
GetEventGenerator()->MoveMouseTo(center);
// Click on the nudge label should toggle the app list.
GetEventGenerator()->ClickLeftButton();
GetAppListTestHelper()->WaitUntilIdle();
GetAppListTestHelper()->CheckVisibility(true);
// Clicking on the nudge label should animate it. Wait until the animation
// ends.
waiter2.WaitUntilAnimationEnded();
// The label is removed after it is clicked.
EXPECT_FALSE(home_button->label_container_for_test());
}
TEST_F(LauncherNudgeControllerTest, AnimationUsedDependsOnAvailableSpace) {
// Set the animation duration mode to non-zero for the launcher nudge
// animation to actually run in the tests.
ui::ScopedAnimationDurationScaleMode non_zero_duration_mode(
ui::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION);
SimulateNewUserFirstLogin("user@gmail.com");
EXPECT_EQ(GetNudgeShownCount(), 0);
HomeButton* home_button = LauncherNudgeController::GetHomeButtonForDisplay(
GetPrimaryDisplay().id());
// Advance the clock to trigger the animation and create the label nudge.
AdvanceClock(nudge_controller_->GetNudgeInterval(/*is_first_time=*/true));
// Without adding anything to shelf, there should be enough space to show
// nudge label.
EXPECT_TRUE(home_button->CanShowNudgeLabel());
int id = 0;
// Add app shortcuts until the hotseat overflow.
while (scrollable_shelf_view_->layout_strategy_for_test() ==
ScrollableShelfView::kNotShowArrowButtons) {
AddAppShortcut(id);
}
// If the apps overflow in shelf, there should be no space for the label to be
// shown.
EXPECT_FALSE(home_button->CanShowNudgeLabel());
}
} // namespace ash