#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/memory/raw_ptr.h"
#include "base/strings/string_number_conversions.h"
#include "base/test/scoped_feature_list.h"
#include "base/test/task_environment.h"
#include "ui/compositor/layer.h"
#include "ui/gfx/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);
}
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;
label_run_loop_.Run();
}
void WaitUntilAnimationEnded() {
DCHECK_GE(started_animation_count_, ended_animation_count_);
if (started_animation_count_ == ended_animation_count_)
return;
animation_run_loop_.Run();
}
int GetShownCount() const { return ended_animation_count_; }
private:
base::RunLoop animation_run_loop_;
base::RunLoop label_run_loop_;
const raw_ptr<HomeButton> home_button_;
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());
}
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);
test_api_->RunMessageLoopUntilAnimationsDone();
}
raw_ptr<LauncherNudgeController, DanglingUntriaged> nudge_controller_;
std::unique_ptr<TestNudgeAnimationObserver> observer_;
raw_ptr<ScrollableShelfView, DanglingUntriaged> scrollable_shelf_view_ =
nullptr;
std::unique_ptr<ShelfViewTestAPI> test_api_;
};
TEST_F(LauncherNudgeControllerTest, DisableNudgeForGuestSession) {
SimulateGuestLogin();
EXPECT_FALSE(nudge_controller_->IsRecheckTimerRunningForTesting());
EXPECT_EQ(0, GetNudgeShownCount());
}
TEST_F(LauncherNudgeControllerTest, NoNudgeWhenSkippedByCommandLineFlag) {
base::CommandLine::ForCurrentProcess()->AppendSwitch(switches::kAshNoNudges);
SimulateUserLogin({"user@gmail.com"});
EXPECT_FALSE(nudge_controller_->IsRecheckTimerRunningForTesting());
EXPECT_EQ(0, GetNudgeShownCount());
}
TEST_F(LauncherNudgeControllerTest, DisableNudgeForExistingUser) {
SimulateUserLogin({"user@gmail.com"});
ASSERT_FALSE(Shell::Get()->session_controller()->IsUserFirstLogin());
EXPECT_FALSE(nudge_controller_->IsRecheckTimerRunningForTesting());
EXPECT_EQ(0, GetNudgeShownCount());
}
TEST_F(LauncherNudgeControllerTest, BasicTest) {
gfx::ScopedAnimationDurationScaleMode non_zero_duration_mode(
gfx::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) {
EXPECT_TRUE(nudge_controller_->IsRecheckTimerRunningForTesting());
if (i == 0) {
AdvanceClock(nudge_controller_->GetNudgeInterval(true));
} else {
AdvanceClock(
nudge_controller_->GetNudgeInterval(false));
}
EXPECT_EQ(i + 1, GetNudgeShownCount());
}
EXPECT_FALSE(nudge_controller_->IsRecheckTimerRunningForTesting());
}
TEST_F(LauncherNudgeControllerTest, StopShowingNudgeAfterLauncherIsOpened) {
gfx::ScopedAnimationDurationScaleMode non_zero_duration_mode(
gfx::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION);
SimulateNewUserFirstLogin("user@gmail.com");
EXPECT_EQ(0, GetNudgeShownCount());
EXPECT_TRUE(nudge_controller_->IsRecheckTimerRunningForTesting());
AdvanceClock(nudge_controller_->GetNudgeInterval(true));
EXPECT_EQ(1, GetNudgeShownCount());
Shell::Get()->app_list_controller()->ToggleAppList(
display::Screen::Get()->GetPrimaryDisplay().id(),
AppListShowSource::kShelfButton, base::TimeTicks());
ASSERT_TRUE(Shell::Get()->app_list_controller()->IsVisible());
AdvanceClock(nudge_controller_->GetNudgeInterval(false));
EXPECT_FALSE(nudge_controller_->IsRecheckTimerRunningForTesting());
EXPECT_EQ(1, GetNudgeShownCount());
}
TEST_F(LauncherNudgeControllerTest, DoNotShowNudgeInTabletMode) {
gfx::ScopedAnimationDurationScaleMode non_zero_duration_mode(
gfx::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION);
SimulateNewUserFirstLogin("user@gmail.com");
EXPECT_EQ(0, GetNudgeShownCount());
Shell::Get()->tablet_mode_controller()->SetEnabledForTest(true);
AdvanceClock(nudge_controller_->GetNudgeInterval(true));
EXPECT_EQ(0, GetNudgeShownCount());
Shell::Get()->tablet_mode_controller()->SetEnabledForTest(false);
EXPECT_EQ(0, GetNudgeShownCount());
AdvanceClock(LauncherNudgeController::kMinIntervalAfterHomeButtonAppears);
EXPECT_EQ(1, GetNudgeShownCount());
}
TEST_F(LauncherNudgeControllerTest, ShowNudgeOnDisplayWhereCursorIsOn) {
gfx::ScopedAnimationDurationScaleMode non_zero_duration_mode(
gfx::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION);
SimulateNewUserFirstLogin("user@gmail.com");
EXPECT_EQ(0, GetNudgeShownCount());
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);
Shell::Get()->cursor_manager()->SetDisplay(GetPrimaryDisplay());
AdvanceClock(nudge_controller_->GetNudgeInterval(true));
waiter_primary.WaitUntilAnimationEnded();
EXPECT_EQ(1, GetNudgeShownCount());
EXPECT_EQ(1, waiter_primary.GetShownCount());
EXPECT_EQ(0, waiter_secondary.GetShownCount());
Shell::Get()->cursor_manager()->SetDisplay(GetSecondaryDisplay());
AdvanceClock(nudge_controller_->GetNudgeInterval(false));
waiter_secondary.WaitUntilAnimationEnded();
EXPECT_EQ(2, GetNudgeShownCount());
EXPECT_EQ(1, waiter_primary.GetShownCount());
EXPECT_EQ(1, waiter_secondary.GetShownCount());
}
TEST_F(LauncherNudgeControllerTest,
WaitUntilHomeButtonStaysLongEnoughToShowNudge) {
gfx::ScopedAnimationDurationScaleMode non_zero_duration_mode(
gfx::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION);
SimulateNewUserFirstLogin("user@gmail.com");
EXPECT_EQ(0, GetNudgeShownCount());
base::TimeDelta small_delta = base::Seconds(10);
AdvanceClock(nudge_controller_->GetNudgeInterval(true) -
small_delta);
ClearLogin();
SimulateUserLogin({"user@gmail.com"});
AdvanceClock(small_delta);
EXPECT_EQ(0, GetNudgeShownCount());
AdvanceClock(LauncherNudgeController::kMinIntervalAfterHomeButtonAppears -
small_delta);
EXPECT_EQ(1, GetNudgeShownCount());
Shell::Get()->tablet_mode_controller()->SetEnabledForTest(true);
AdvanceClock(nudge_controller_->GetNudgeInterval(true) -
small_delta);
Shell::Get()->tablet_mode_controller()->SetEnabledForTest(false);
AdvanceClock(small_delta);
EXPECT_EQ(1, GetNudgeShownCount());
AdvanceClock(LauncherNudgeController::kMinIntervalAfterHomeButtonAppears -
small_delta);
EXPECT_EQ(2, GetNudgeShownCount());
}
TEST_F(LauncherNudgeControllerTest, NudgeLabelVisibilityTest) {
gfx::ScopedAnimationDurationScaleMode non_zero_duration_mode(
gfx::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(true));
waiter.WaitUntilLabelShown();
views::View* label_container = home_button->expandable_container_for_test();
EXPECT_TRUE(label_container && label_container->GetVisible());
EXPECT_EQ(label_container->layer()->opacity(), 1);
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(false) -
base::Seconds(6));
waiter2.WaitUntilLabelShown();
EXPECT_TRUE(label_container->GetVisible());
gfx::Point center = label_container->GetBoundsInScreen().CenterPoint();
GetEventGenerator()->MoveMouseTo(center);
GetEventGenerator()->ClickLeftButton();
GetAppListTestHelper()->WaitUntilIdle();
GetAppListTestHelper()->CheckVisibility(true);
waiter2.WaitUntilAnimationEnded();
EXPECT_FALSE(home_button->expandable_container_for_test());
}
TEST_F(LauncherNudgeControllerTest, AnimationUsedDependsOnAvailableSpace) {
gfx::ScopedAnimationDurationScaleMode non_zero_duration_mode(
gfx::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION);
SimulateNewUserFirstLogin("user@gmail.com");
EXPECT_EQ(GetNudgeShownCount(), 0);
HomeButton* home_button = LauncherNudgeController::GetHomeButtonForDisplay(
GetPrimaryDisplay().id());
AdvanceClock(nudge_controller_->GetNudgeInterval(true));
EXPECT_TRUE(home_button->CanShowNudgeLabel());
int id = 0;
while (scrollable_shelf_view_->layout_strategy_for_test() ==
ScrollableShelfView::kNotShowArrowButtons) {
AddAppShortcut(id);
}
EXPECT_FALSE(home_button->CanShowNudgeLabel());
}
}