#include "ash/system/status_area_animation_controller.h"
#include <algorithm>
#include "ash/ime/ime_controller_impl.h"
#include "ash/shell.h"
#include "ash/system/notification_center/notification_center_test_api.h"
#include "ash/system/notification_center/notification_center_tray.h"
#include "ash/system/status_area_widget.h"
#include "ash/system/status_area_widget_test_helper.h"
#include "ash/system/unified/notification_counter_view.h"
#include "ash/system/unified/notification_icons_controller.h"
#include "ash/test/ash_test_base.h"
#include "base/memory/raw_ptr.h"
#include "base/run_loop.h"
#include "base/test/task_environment.h"
#include "ui/compositor/test/layer_animation_stopped_waiter.h"
#include "ui/display/manager/display_manager.h"
#include "ui/gfx/image/image_skia.h"
#include "ui/gfx/scoped_animation_duration_scale_mode.h"
#include "ui/message_center/message_center.h"
namespace ash {
class TrayItemViewAnimationWaiter {
public:
explicit TrayItemViewAnimationWaiter(TrayItemView* tray_item)
: tray_item_(tray_item) {}
TrayItemViewAnimationWaiter(const TrayItemViewAnimationWaiter&) = delete;
TrayItemViewAnimationWaiter& operator=(const TrayItemViewAnimationWaiter&) =
delete;
~TrayItemViewAnimationWaiter() = default;
void Wait() {
if (tray_item_->IsAnimating()) {
tray_item_->SetAnimationIdleClosureForTest(base::BindOnce(
&TrayItemViewAnimationWaiter::OnTrayItemAnimationFinished,
weak_ptr_factory_.GetWeakPtr()));
run_loop_.Run();
}
}
private:
void OnTrayItemAnimationFinished() { run_loop_.Quit(); }
raw_ptr<TrayItemView> tray_item_ = nullptr;
base::RunLoop run_loop_;
base::WeakPtrFactory<TrayItemViewAnimationWaiter> weak_ptr_factory_{this};
};
class StatusAreaAnimationControllerTest : public AshTestBase {
public:
StatusAreaAnimationControllerTest()
: AshTestBase(base::test::TaskEnvironment::TimeSource::MOCK_TIME) {
}
StatusAreaAnimationControllerTest(const StatusAreaAnimationControllerTest&) =
delete;
StatusAreaAnimationControllerTest& operator=(
const StatusAreaAnimationControllerTest&) = delete;
~StatusAreaAnimationControllerTest() override = default;
void SetUp() override {
AshTestBase::SetUp();
test_api = std::make_unique<NotificationCenterTestApi>();
base::RunLoop().RunUntilIdle();
ASSERT_TRUE(test_api->GetTray()->IsShowAnimationEnabled());
}
bool IsNotificationCounterAnimationRunning() {
return notification_counter()->IsAnimating();
}
bool IsCapsLockTrayItemAnimationRunning() {
return caps_lock_tray_item()->IsAnimating();
}
NotificationCounterView* notification_counter() {
return test_api->GetTray()
->notification_icons_controller()
->notification_counter_view();
}
NotificationIconTrayItemView* caps_lock_tray_item() {
return test_api->GetTray()
->notification_icons_controller()
->tray_items()
.back();
}
std::unique_ptr<NotificationCenterTestApi> test_api;
};
TEST_F(StatusAreaAnimationControllerTest,
TrayItemAnimationDisabledDuringShowAnimation) {
gfx::ScopedAnimationDurationScaleMode test_duration_mode(
gfx::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION);
ASSERT_FALSE(test_api->IsTrayAnimating());
ASSERT_FALSE(test_api->IsTrayShown());
ASSERT_FALSE(notification_counter()->GetVisible());
test_api->AddNotification();
ASSERT_TRUE(test_api->IsTrayAnimating());
ASSERT_TRUE(test_api->IsTrayShown());
EXPECT_FALSE(IsNotificationCounterAnimationRunning());
ui::LayerAnimationStoppedWaiter notification_center_tray_waiter;
notification_center_tray_waiter.Wait(test_api->GetTray()->layer());
ASSERT_FALSE(test_api->IsTrayAnimating());
EXPECT_TRUE(notification_counter()->GetVisible());
EXPECT_EQ(notification_counter()->layer()->opacity(), 1);
}
TEST_F(StatusAreaAnimationControllerTest,
TrayItemAnimationDisabledDuringShowAnimationOnSecondaryDisplay) {
gfx::ScopedAnimationDurationScaleMode test_duration_mode(
gfx::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION);
UpdateDisplay("800x799,800x799");
auto secondary_display_id = display_manager()->GetDisplayAt(1).id();
test_api->AddNotification();
ASSERT_TRUE(test_api->IsTrayShown());
ASSERT_TRUE(test_api->IsTrayShownOnDisplay(secondary_display_id));
ASSERT_TRUE(test_api->IsTrayAnimating());
ASSERT_TRUE(test_api->IsTrayAnimatingOnDisplay(secondary_display_id));
EXPECT_FALSE(
test_api->IsNotificationCounterAnimatingOnDisplay(secondary_display_id));
ui::LayerAnimationStoppedWaiter notification_center_tray_waiter;
notification_center_tray_waiter.Wait(
test_api->GetTrayOnDisplay(secondary_display_id)->layer());
ASSERT_FALSE(test_api->IsTrayAnimatingOnDisplay(secondary_display_id));
EXPECT_TRUE(
test_api->IsNotificationCounterShownOnDisplay(secondary_display_id));
EXPECT_EQ(test_api->GetNotificationCounterOnDisplay(secondary_display_id)
->layer()
->opacity(),
1);
}
TEST_F(StatusAreaAnimationControllerTest,
TrayItemAnimationDisabledDuringHideAnimation) {
gfx::ScopedAnimationDurationScaleMode test_duration_mode(
gfx::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION);
ASSERT_FALSE(test_api->IsTrayAnimating());
ASSERT_FALSE(test_api->IsTrayShown());
ASSERT_FALSE(notification_counter()->GetVisible());
auto id = test_api->AddNotification();
ASSERT_TRUE(test_api->IsTrayAnimating());
ASSERT_TRUE(test_api->IsTrayShown());
ui::LayerAnimationStoppedWaiter notification_center_tray_waiter;
notification_center_tray_waiter.Wait(test_api->GetTray()->layer());
ASSERT_FALSE(test_api->IsTrayAnimating());
ASSERT_TRUE(test_api->IsTrayShown());
test_api->RemoveNotification(id);
ASSERT_TRUE(test_api->IsTrayAnimating());
ASSERT_FALSE(test_api->GetTray()->layer()->GetTargetVisibility());
ASSERT_EQ(test_api->GetTray()->layer()->GetTargetOpacity(), 0);
EXPECT_FALSE(IsNotificationCounterAnimationRunning());
notification_center_tray_waiter.Wait(test_api->GetTray()->layer());
ASSERT_FALSE(test_api->IsTrayAnimating());
EXPECT_FALSE(notification_counter()->GetVisible());
EXPECT_EQ(notification_counter()->layer()->opacity(), 0);
}
TEST_F(StatusAreaAnimationControllerTest,
NotificationIconNotResetWhileNotificationTrayHideAnimationRunning) {
auto id = test_api->AddCriticalWarningSystemNotification();
auto* notification_icon = test_api->GetNotificationIconForId(id);
CHECK(notification_icon);
auto icon_image = notification_icon->image_view()->GetImage();
ASSERT_TRUE(test_api->IsNotificationIconShown());
ASSERT_TRUE(test_api->IsTrayShown());
ASSERT_FALSE(test_api->IsTrayAnimating());
gfx::ScopedAnimationDurationScaleMode test_duration_mode(
gfx::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION);
test_api->RemoveNotification(id);
ASSERT_TRUE(test_api->IsTrayAnimating());
ASSERT_FALSE(test_api->GetTray()->layer()->GetTargetVisibility());
ASSERT_EQ(test_api->GetTray()->layer()->GetTargetOpacity(), 0);
EXPECT_TRUE(icon_image.BackedBySameObjectAs(
notification_icon->image_view()->GetImage()));
ui::LayerAnimationStoppedWaiter notification_center_tray_waiter;
notification_center_tray_waiter.Wait(test_api->GetTray()->layer());
ASSERT_FALSE(test_api->IsTrayAnimating());
EXPECT_TRUE(notification_icon->image_view()->GetImage().BackedBySameObjectAs(
gfx::ImageSkia()));
}
TEST_F(StatusAreaAnimationControllerTest,
NotificationIconNotResetWhileHideAnimationRunning) {
test_api->AddNotification();
auto id = test_api->AddCriticalWarningSystemNotification();
auto* notification_icon = test_api->GetNotificationIconForId(id);
CHECK(notification_icon);
auto icon_image = notification_icon->image_view()->GetImage();
ASSERT_TRUE(test_api->IsNotificationCounterShown());
ASSERT_TRUE(test_api->IsNotificationIconShown());
ASSERT_TRUE(test_api->IsTrayShown());
ASSERT_FALSE(test_api->IsTrayAnimating());
gfx::ScopedAnimationDurationScaleMode test_duration_mode(
gfx::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION);
test_api->RemoveNotification(id);
ASSERT_FALSE(test_api->IsTrayAnimating());
ASSERT_FALSE(test_api->IsNotificationCounterAnimating());
ASSERT_TRUE(notification_icon->IsAnimating());
ASSERT_FALSE(notification_icon->target_visible_for_testing());
EXPECT_TRUE(icon_image.BackedBySameObjectAs(
notification_icon->image_view()->GetImage()));
auto* icon_animation = notification_icon->animation_for_testing();
icon_animation->End();
ASSERT_FALSE(notification_icon->IsAnimating());
EXPECT_TRUE(notification_icon->image_view()->GetImage().BackedBySameObjectAs(
gfx::ImageSkia()));
}
TEST_F(StatusAreaAnimationControllerTest,
TrayItemAnimationDisabledDuringNonInitialShowAnimation) {
gfx::ScopedAnimationDurationScaleMode test_duration_mode(
gfx::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION);
ASSERT_FALSE(test_api->IsTrayAnimating());
ASSERT_FALSE(test_api->IsTrayShown());
ASSERT_FALSE(notification_counter()->GetVisible());
auto id = test_api->AddNotification();
ASSERT_TRUE(test_api->IsTrayAnimating());
ui::LayerAnimationStoppedWaiter notification_center_tray_waiter;
notification_center_tray_waiter.Wait(test_api->GetTray()->layer());
ASSERT_FALSE(test_api->IsTrayAnimating());
test_api->RemoveNotification(id);
ASSERT_TRUE(test_api->IsTrayAnimating());
notification_center_tray_waiter.Wait(test_api->GetTray()->layer());
ASSERT_FALSE(test_api->IsTrayAnimating());
ASSERT_FALSE(test_api->IsTrayShown());
ASSERT_FALSE(notification_counter()->GetVisible());
test_api->AddNotification();
ASSERT_TRUE(test_api->IsTrayAnimating());
ASSERT_TRUE(test_api->IsTrayShown());
EXPECT_FALSE(IsNotificationCounterAnimationRunning());
notification_center_tray_waiter.Wait(test_api->GetTray()->layer());
ASSERT_FALSE(test_api->IsTrayAnimating());
EXPECT_TRUE(notification_counter()->GetVisible());
EXPECT_EQ(notification_counter()->layer()->opacity(), 1);
}
TEST_F(StatusAreaAnimationControllerTest,
TrayItemAnimationDisabledDuringNonInitialHideAnimation) {
gfx::ScopedAnimationDurationScaleMode test_duration_mode(
gfx::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION);
ASSERT_FALSE(test_api->IsTrayAnimating());
ASSERT_FALSE(test_api->IsTrayShown());
ASSERT_FALSE(notification_counter()->GetVisible());
auto id = test_api->AddNotification();
ASSERT_TRUE(test_api->IsTrayAnimating());
ui::LayerAnimationStoppedWaiter notification_center_tray_waiter;
notification_center_tray_waiter.Wait(test_api->GetTray()->layer());
ASSERT_FALSE(test_api->IsTrayAnimating());
ASSERT_TRUE(test_api->IsTrayShown());
ASSERT_TRUE(notification_counter()->GetVisible());
test_api->RemoveNotification(id);
ASSERT_TRUE(test_api->IsTrayAnimating());
notification_center_tray_waiter.Wait(test_api->GetTray()->layer());
ASSERT_FALSE(test_api->IsTrayAnimating());
ASSERT_FALSE(test_api->IsTrayShown());
ASSERT_FALSE(notification_counter()->GetVisible());
id = test_api->AddNotification();
ASSERT_TRUE(test_api->IsTrayAnimating());
notification_center_tray_waiter.Wait(test_api->GetTray()->layer());
ASSERT_FALSE(test_api->IsTrayAnimating());
ASSERT_TRUE(test_api->IsTrayShown());
ASSERT_TRUE(notification_counter()->GetVisible());
test_api->RemoveNotification(id);
ASSERT_TRUE(test_api->IsTrayAnimating());
EXPECT_FALSE(IsNotificationCounterAnimationRunning());
notification_center_tray_waiter.Wait(test_api->GetTray()->layer());
ASSERT_FALSE(test_api->IsTrayAnimating());
ASSERT_FALSE(test_api->IsTrayShown());
EXPECT_FALSE(notification_counter()->GetVisible());
EXPECT_EQ(notification_counter()->layer()->opacity(), 0);
}
TEST_F(StatusAreaAnimationControllerTest,
VisibleTrayItemDoesNotAnimateDuringNewTrayItemAnimation) {
gfx::ScopedAnimationDurationScaleMode test_duration_mode(
gfx::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION);
ASSERT_FALSE(test_api->IsTrayAnimating());
ASSERT_FALSE(test_api->IsTrayShown());
test_api->AddNotification();
ASSERT_TRUE(test_api->IsTrayAnimating());
ui::LayerAnimationStoppedWaiter notification_center_tray_waiter;
notification_center_tray_waiter.Wait(test_api->GetTray()->layer());
ASSERT_TRUE(notification_counter()->GetVisible());
ASSERT_FALSE(IsNotificationCounterAnimationRunning());
Shell::Get()->ime_controller()->UpdateCapsLockState(true);
EXPECT_TRUE(IsCapsLockTrayItemAnimationRunning());
EXPECT_FALSE(IsNotificationCounterAnimationRunning());
TrayItemViewAnimationWaiter(caps_lock_tray_item()).Wait();
EXPECT_FALSE(IsCapsLockTrayItemAnimationRunning());
EXPECT_TRUE(caps_lock_tray_item()->GetVisible());
EXPECT_EQ(caps_lock_tray_item()->layer()->opacity(), 1);
EXPECT_TRUE(notification_counter()->GetVisible());
EXPECT_EQ(notification_counter()->layer()->opacity(), 1);
}
TEST_F(StatusAreaAnimationControllerTest,
VisibleTrayItemAnimatesOutWithoutCausingWholeTrayAnimation) {
gfx::ScopedAnimationDurationScaleMode test_duration_mode(
gfx::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION);
ASSERT_FALSE(test_api->IsTrayAnimating());
ASSERT_FALSE(test_api->IsTrayShown());
auto id = test_api->AddNotification();
ASSERT_TRUE(test_api->IsTrayAnimating());
ui::LayerAnimationStoppedWaiter notification_center_tray_waiter;
notification_center_tray_waiter.Wait(test_api->GetTray()->layer());
ASSERT_FALSE(test_api->IsTrayAnimating());
ASSERT_TRUE(notification_counter()->GetVisible());
Shell::Get()->ime_controller()->UpdateCapsLockState(true);
TrayItemViewAnimationWaiter(caps_lock_tray_item()).Wait();
ASSERT_FALSE(IsCapsLockTrayItemAnimationRunning());
ASSERT_TRUE(caps_lock_tray_item()->GetVisible());
test_api->RemoveNotification(id);
ASSERT_EQ(test_api->GetNotificationCount(), 1u);
EXPECT_TRUE(IsNotificationCounterAnimationRunning());
EXPECT_TRUE(test_api->GetTray()->GetVisible());
EXPECT_FALSE(test_api->IsTrayAnimating());
TrayItemViewAnimationWaiter(notification_counter()).Wait();
EXPECT_FALSE(IsNotificationCounterAnimationRunning());
EXPECT_FALSE(notification_counter()->GetVisible());
EXPECT_TRUE(test_api->GetTray()->GetVisible());
}
TEST_F(StatusAreaAnimationControllerTest, ShowWhileHideAnimationIsRunning) {
auto id = test_api->AddNotification();
ASSERT_TRUE(test_api->IsTrayShown());
ASSERT_FALSE(test_api->IsTrayAnimating());
gfx::ScopedAnimationDurationScaleMode test_duration_mode(
gfx::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION);
test_api->RemoveNotification(id);
EXPECT_TRUE(test_api->IsTrayAnimating());
EXPECT_FALSE(test_api->GetTray()->layer()->GetTargetVisibility());
EXPECT_EQ(test_api->GetTray()->layer()->GetTargetOpacity(), 0);
test_api->AddNotification();
EXPECT_TRUE(test_api->IsTrayAnimating());
EXPECT_TRUE(test_api->GetTray()->layer()->GetTargetVisibility());
EXPECT_EQ(test_api->GetTray()->layer()->GetTargetOpacity(), 1);
ui::LayerAnimationStoppedWaiter notification_center_tray_waiter;
notification_center_tray_waiter.Wait(test_api->GetTray()->layer());
ASSERT_FALSE(test_api->IsTrayAnimating());
EXPECT_TRUE(test_api->GetTray()->GetVisible());
EXPECT_EQ(test_api->GetTray()->layer()->opacity(), 1);
}
TEST_F(StatusAreaAnimationControllerTest, HideWhileShowAnimationIsRunning) {
ASSERT_FALSE(test_api->IsTrayShown());
ASSERT_FALSE(test_api->IsTrayAnimating());
gfx::ScopedAnimationDurationScaleMode test_duration_mode(
gfx::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION);
auto id = test_api->AddNotification();
ASSERT_TRUE(test_api->IsTrayAnimating());
ASSERT_TRUE(test_api->GetTray()->layer()->GetTargetVisibility());
ASSERT_EQ(test_api->GetTray()->layer()->GetTargetOpacity(), 1);
test_api->RemoveNotification(id);
EXPECT_TRUE(test_api->IsTrayAnimating());
EXPECT_FALSE(test_api->GetTray()->layer()->GetTargetVisibility());
EXPECT_EQ(test_api->GetTray()->layer()->GetTargetOpacity(), 0);
ui::LayerAnimationStoppedWaiter notification_center_tray_waiter;
notification_center_tray_waiter.Wait(test_api->GetTray()->layer());
ASSERT_FALSE(test_api->IsTrayAnimating());
EXPECT_FALSE(test_api->IsTrayShown());
EXPECT_EQ(test_api->GetTray()->layer()->opacity(), 0);
}
TEST_F(StatusAreaAnimationControllerTest,
HideAnimationEndedResetsTrayItemAnimation) {
auto id = test_api->AddNotification();
ASSERT_TRUE(test_api->IsTrayShown());
ASSERT_FALSE(test_api->IsTrayAnimating());
gfx::ScopedAnimationDurationScaleMode test_duration_mode(
gfx::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION);
message_center::MessageCenter::Get()->SetQuietMode(true);
message_center::MessageCenter::Get()->SetQuietMode(false);
ASSERT_EQ(notification_counter()->animation_for_testing()->GetCurrentValue(),
1);
test_api->RemoveNotification(id);
ASSERT_TRUE(test_api->IsTrayAnimating());
ui::LayerAnimationStoppedWaiter notification_center_tray_waiter;
notification_center_tray_waiter.Wait(test_api->GetTray()->layer());
ASSERT_FALSE(test_api->IsTrayAnimating());
EXPECT_EQ(notification_counter()->animation_for_testing()->GetCurrentValue(),
0);
}
}