#include "ash/booting/booting_animation_controller.h"
#include <memory>
#include "ash/booting/booting_animation_view.h"
#include "ash/constants/ash_features.h"
#include "ash/public/cpp/shell_window_ids.h"
#include "ash/shell.h"
#include "base/files/file_util.h"
#include "base/location.h"
#include "base/system/sys_info.h"
#include "base/task/task_traits.h"
#include "base/task/thread_pool.h"
#include "base/time/time.h"
#include "ui/base/mojom/window_show_state.mojom.h"
#include "ui/views/widget/widget.h"
#include "ui/views/widget/widget_delegate.h"
namespace ash {
namespace {
constexpr base::FilePath::CharType kAnimationPath[] = FILE_PATH_LITERAL(
"/usr/share/chromeos-assets/animated_splash_screen/splash_animation.json");
std::string ReadFileToString(const base::FilePath& path) {
std::string result;
if (!base::ReadFileToString(path, &result)) {
LOG(WARNING) << "Failed reading file";
result.clear();
}
return result;
}
}
BootingAnimationController::BootingAnimationController() {
CHECK(ash::Shell::Get()->display_configurator());
scoped_display_configurator_observer_.Observe(
ash::Shell::Get()->display_configurator());
base::ThreadPool::PostTaskAndReplyWithResult(
FROM_HERE,
{base::MayBlock(), base::TaskPriority::USER_VISIBLE,
base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN},
base::BindOnce(&ReadFileToString, base::FilePath(kAnimationPath)),
base::BindOnce(&BootingAnimationController::OnAnimationDataFetched,
weak_factory_.GetWeakPtr()));
}
BootingAnimationController::~BootingAnimationController() = default;
void BootingAnimationController::Show() {
if (data_fetch_failed_.has_value() && data_fetch_failed_.value()) {
std::move(animation_played_callback_).Run();
return;
}
widget_ = std::make_unique<views::Widget>();
views::Widget::InitParams params(
views::Widget::InitParams::NATIVE_WIDGET_OWNS_WIDGET,
views::Widget::InitParams::TYPE_WINDOW_FRAMELESS);
params.delegate = new views::WidgetDelegate;
params.delegate->SetOwnedByWidget(
views::WidgetDelegate::OwnedByWidgetPassKey());
params.delegate->SetCanMaximize(true);
params.delegate->SetCanFullscreen(true);
params.name = "BootingAnimationWidget";
params.show_state = ui::mojom::WindowShowState::kFullscreen;
auto* animation_window = Shell::GetContainer(
Shell::GetPrimaryRootWindow(), kShellWindowId_BootingAnimationContainer);
params.parent = animation_window;
params.ownership = views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
params.opacity = views::Widget::InitParams::WindowOpacity::kTranslucent;
widget_->Init(std::move(params));
widget_->SetContentsView(std::make_unique<BootingAnimationView>());
widget_->Show();
}
void BootingAnimationController::ShowAnimationWithEndCallback(
base::OnceClosure callback) {
animation_played_callback_ = std::move(callback);
Show();
if (!base::SysInfo::IsRunningOnChromeOS()) {
IgnoreGpuReadiness();
return;
}
if (!IsDeviceReady()) {
base::SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask(
FROM_HERE,
base::BindOnce(&BootingAnimationController::IgnoreGpuReadiness,
weak_factory_.GetWeakPtr()),
base::TimeDelta(base::Seconds(5)));
return;
}
StartAnimation();
}
void BootingAnimationController::Finish() {
widget_.reset();
animation_played_callback_.Reset();
}
base::WeakPtr<BootingAnimationController>
BootingAnimationController::GetWeakPtr() {
return weak_factory_.GetWeakPtr();
}
void BootingAnimationController::OnDisplayConfigurationChanged(
const display::DisplayConfigurator::DisplayStateList& displays) {
if (!is_gpu_ready_) {
return;
}
scoped_display_configurator_observer_.Reset();
CHECK(IsDeviceReady());
if (!animation_played_callback_.is_null()) {
StartAnimation();
}
}
void BootingAnimationController::OnDisplaySnapshotsInvalidated() {
is_gpu_ready_ = true;
}
void BootingAnimationController::AnimationCycleEnded(
const lottie::Animation* animation) {
scoped_animation_observer_.Reset();
if (!animation_played_callback_.is_null()) {
std::move(animation_played_callback_).Run();
}
}
void BootingAnimationController::OnAnimationDataFetched(std::string data) {
if (data.empty()) {
LOG(ERROR) << "No booting animation file available.";
data_fetch_failed_ = true;
if (!animation_played_callback_.is_null()) {
std::move(animation_played_callback_).Run();
}
return;
}
data_fetch_failed_ = false;
animation_data_ = std::move(data);
if (!animation_played_callback_.is_null() && IsDeviceReady()) {
StartAnimation();
}
}
void BootingAnimationController::StartAnimation() {
if (!data_fetch_failed_.has_value()) {
LOG(ERROR) << "Booting animation isn't ready yet.";
return;
}
CHECK(!animation_played_callback_.is_null() && IsDeviceReady());
if (was_shown_) {
return;
}
was_shown_ = true;
BootingAnimationView* view =
static_cast<BootingAnimationView*>(widget_->GetContentsView());
view->SetAnimatedImage(animation_data_);
auto* animated_image = view->GetAnimatedImage();
if (!animated_image) {
std::move(animation_played_callback_).Run();
return;
}
scoped_animation_observer_.Observe(animated_image);
view->Play();
}
void BootingAnimationController::IgnoreGpuReadiness() {
if (IsDeviceReady()) {
return;
}
LOG(ERROR) << "Ignore the readiness of the GPU and play the animation.";
is_gpu_ready_ = true;
scoped_display_configurator_observer_.Reset();
if (!animation_played_callback_.is_null()) {
StartAnimation();
}
}
bool BootingAnimationController::IsDeviceReady() const {
return is_gpu_ready_ && !scoped_display_configurator_observer_.IsObserving();
}
}