// Copyright 2022 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/ambient/ui/ambient_animation_progress_tracker.h"
#include "base/memory/scoped_refptr.h"
#include "base/time/time.h"
#include "cc/paint/skottie_wrapper.h"
#include "cc/test/skia_common.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/gfx/canvas.h"
#include "ui/gfx/geometry/size.h"
#include "ui/lottie/animation.h"
namespace ash {
namespace {
constexpr float kTimestampEpsilon = .001f;
using ::testing::AllOf;
using ::testing::Eq;
using ::testing::FloatEq;
using ::testing::Ge;
using ::testing::Le;
using ::testing::SizeIs;
class AmbientAnimationProgressTrackerTest : public ::testing::Test {
protected:
static constexpr gfx::Size kAnimationSize = gfx::Size(100, 100);
AmbientAnimationProgressTrackerTest()
: clock_(base::TimeTicks::Now()),
canvas_(kAnimationSize, /*image_scale=*/1.f, /*is_opaque=*/false) {}
scoped_refptr<cc::SkottieWrapper> CreateSkottie(int duration_secs) {
return cc::CreateSkottie(kAnimationSize, duration_secs);
}
void Paint(lottie::Animation& animation) {
animation.Paint(&canvas_, clock_, kAnimationSize);
}
base::TimeTicks clock_;
gfx::Canvas canvas_;
AmbientAnimationProgressTracker tracker_;
};
TEST_F(AmbientAnimationProgressTrackerTest, SingleAnimation) {
AmbientAnimationProgressTracker tracker;
lottie::Animation animation(CreateSkottie(/*duration_secs=*/10));
tracker.RegisterAnimation(&animation);
ASSERT_FALSE(tracker.HasActiveAnimations());
animation.Start();
Paint(animation);
ASSERT_TRUE(tracker.HasActiveAnimations());
AmbientAnimationProgressTracker::ImmutableParams immutable_params =
tracker.GetImmutableParams();
AmbientAnimationProgressTracker::Progress global_progress =
tracker.GetGlobalProgress();
EXPECT_THAT(global_progress.num_completed_cycles, Eq(0));
EXPECT_THAT(global_progress.current_timestamp, FloatEq(0.f));
EXPECT_THAT(immutable_params.total_duration, Eq(base::Seconds(10)));
ASSERT_THAT(immutable_params.scheduled_cycles, SizeIs(1));
EXPECT_THAT(immutable_params.scheduled_cycles.front().start_offset,
Eq(base::TimeDelta()));
EXPECT_THAT(immutable_params.scheduled_cycles.front().end_offset,
Eq(base::Seconds(10)));
EXPECT_THAT(immutable_params.style, Eq(lottie::Animation::Style::kLoop));
clock_ += base::Seconds(5);
Paint(animation);
global_progress = tracker.GetGlobalProgress();
EXPECT_THAT(global_progress.num_completed_cycles, Eq(0));
EXPECT_THAT(global_progress.current_timestamp, FloatEq(.5f));
clock_ += base::Seconds(5);
Paint(animation);
global_progress = tracker.GetGlobalProgress();
EXPECT_THAT(global_progress.num_completed_cycles, Eq(1));
EXPECT_THAT(global_progress.current_timestamp, FloatEq(0.f));
clock_ += base::Seconds(5);
Paint(animation);
global_progress = tracker.GetGlobalProgress();
EXPECT_THAT(global_progress.num_completed_cycles, Eq(1));
EXPECT_THAT(global_progress.current_timestamp, FloatEq(.5f));
}
TEST_F(AmbientAnimationProgressTrackerTest, MultipleAnimations) {
AmbientAnimationProgressTracker tracker;
lottie::Animation animation_1(CreateSkottie(/*duration_secs=*/10));
lottie::Animation animation_2(CreateSkottie(/*duration_secs=*/10));
tracker.RegisterAnimation(&animation_1);
tracker.RegisterAnimation(&animation_2);
ASSERT_FALSE(tracker.HasActiveAnimations());
animation_1.Start();
animation_2.Start();
Paint(animation_1);
Paint(animation_2);
ASSERT_TRUE(tracker.HasActiveAnimations());
AmbientAnimationProgressTracker::ImmutableParams immutable_params =
tracker.GetImmutableParams();
AmbientAnimationProgressTracker::Progress global_progress =
tracker.GetGlobalProgress();
EXPECT_THAT(global_progress.num_completed_cycles, Eq(0));
EXPECT_THAT(global_progress.current_timestamp, FloatEq(0.f));
EXPECT_THAT(immutable_params.total_duration, Eq(base::Seconds(10)));
ASSERT_THAT(immutable_params.scheduled_cycles, SizeIs(1));
EXPECT_THAT(immutable_params.scheduled_cycles.front().start_offset,
Eq(base::TimeDelta()));
EXPECT_THAT(immutable_params.scheduled_cycles.front().end_offset,
Eq(base::Seconds(10)));
EXPECT_THAT(immutable_params.style, Eq(lottie::Animation::Style::kLoop));
clock_ += base::Seconds(5);
Paint(animation_1);
clock_ += base::Milliseconds(100);
Paint(animation_2);
global_progress = tracker.GetGlobalProgress();
EXPECT_THAT(global_progress.num_completed_cycles, Eq(0));
EXPECT_THAT(global_progress.current_timestamp,
AllOf(Ge(.5f - kTimestampEpsilon), Le(.51f + kTimestampEpsilon)));
clock_ += base::Seconds(5) - base::Milliseconds(100);
Paint(animation_1);
Paint(animation_2);
global_progress = tracker.GetGlobalProgress();
EXPECT_THAT(global_progress.num_completed_cycles, Eq(1));
EXPECT_THAT(global_progress.current_timestamp, FloatEq(.0f));
clock_ += base::Seconds(2);
Paint(animation_2);
clock_ += base::Seconds(1);
Paint(animation_1);
global_progress = tracker.GetGlobalProgress();
EXPECT_THAT(global_progress.num_completed_cycles, Eq(1));
EXPECT_THAT(global_progress.current_timestamp,
AllOf(Ge(.2f - kTimestampEpsilon), Le(.3f + kTimestampEpsilon)));
}
TEST_F(AmbientAnimationProgressTrackerTest, AnimationsDestroyed) {
AmbientAnimationProgressTracker tracker;
auto animation_1 =
std::make_unique<lottie::Animation>(CreateSkottie(/*duration_secs=*/10));
auto animation_2 =
std::make_unique<lottie::Animation>(CreateSkottie(/*duration_secs=*/10));
tracker.RegisterAnimation(animation_1.get());
tracker.RegisterAnimation(animation_2.get());
animation_1->Start();
animation_2->Start();
Paint(*animation_1);
Paint(*animation_2);
clock_ += base::Seconds(5);
Paint(*animation_1);
clock_ += base::Milliseconds(100);
Paint(*animation_2);
animation_1.reset();
AmbientAnimationProgressTracker::Progress global_progress =
tracker.GetGlobalProgress();
EXPECT_THAT(global_progress.num_completed_cycles, Eq(0));
EXPECT_THAT(global_progress.current_timestamp, FloatEq(.51f));
animation_2.reset();
EXPECT_FALSE(tracker.HasActiveAnimations());
auto animation_3 =
std::make_unique<lottie::Animation>(CreateSkottie(/*duration_secs=*/20));
auto animation_4 =
std::make_unique<lottie::Animation>(CreateSkottie(/*duration_secs=*/20));
tracker.RegisterAnimation(animation_3.get());
tracker.RegisterAnimation(animation_4.get());
animation_3->Start();
animation_4->Start();
Paint(*animation_3);
Paint(*animation_4);
clock_ += base::Seconds(5);
Paint(*animation_3);
Paint(*animation_4);
global_progress = tracker.GetGlobalProgress();
EXPECT_THAT(global_progress.num_completed_cycles, Eq(0));
EXPECT_THAT(global_progress.current_timestamp, FloatEq(.25f));
}
TEST_F(AmbientAnimationProgressTrackerTest, AnimationRestarted) {
AmbientAnimationProgressTracker tracker;
auto animation =
std::make_unique<lottie::Animation>(CreateSkottie(/*duration_secs=*/10));
tracker.RegisterAnimation(animation.get());
animation->Start();
Paint(*animation);
clock_ += base::Seconds(5);
Paint(*animation);
ASSERT_TRUE(tracker.HasActiveAnimations());
EXPECT_THAT(tracker.GetGlobalProgress().current_timestamp, FloatEq(0.5f));
animation->Stop();
EXPECT_FALSE(tracker.HasActiveAnimations());
// Should be a no-op.
tracker.RegisterAnimation(animation.get());
animation->Start();
Paint(*animation);
ASSERT_TRUE(tracker.HasActiveAnimations());
EXPECT_THAT(tracker.GetGlobalProgress().current_timestamp, FloatEq(0.f));
}
} // namespace
} // namespace ash