#include "ash/system/holding_space/holding_space_animation_registry.h"
#include <algorithm>
#include <map>
#include <memory>
#include <set>
#include <vector>
#include "ash/public/cpp/holding_space/holding_space_controller.h"
#include "ash/public/cpp/holding_space/holding_space_controller_observer.h"
#include "ash/public/cpp/holding_space/holding_space_item_updated_fields.h"
#include "ash/public/cpp/holding_space/holding_space_model.h"
#include "ash/public/cpp/holding_space/holding_space_model_observer.h"
#include "ash/shell.h"
#include "ash/system/progress_indicator/progress_icon_animation.h"
#include "ash/system/progress_indicator/progress_ring_animation.h"
#include "base/containers/contains.h"
#include "base/memory/ptr_util.h"
#include "base/memory/raw_ptr.h"
#include "base/no_destructor.h"
#include "base/task/sequenced_task_runner.h"
namespace ash {
namespace {
std::unique_ptr<HoldingSpaceAnimationRegistry>& GetInstanceOwner() {
static base::NoDestructor<std::unique_ptr<HoldingSpaceAnimationRegistry>>
instance_owner;
return *instance_owner;
}
}
class HoldingSpaceAnimationRegistry::ProgressIndicatorAnimationDelegate
: public HoldingSpaceControllerObserver,
public HoldingSpaceModelObserver {
public:
ProgressIndicatorAnimationDelegate(
ProgressIndicatorAnimationRegistry* registry,
HoldingSpaceController* controller)
: registry_(registry), controller_(controller) {
controller_observation_.Observe(controller_.get());
if (controller_->model())
OnHoldingSpaceModelAttached(controller_->model());
}
ProgressIndicatorAnimationDelegate(
const ProgressIndicatorAnimationDelegate&) = delete;
ProgressIndicatorAnimationDelegate& operator=(
const ProgressIndicatorAnimationDelegate&) = delete;
~ProgressIndicatorAnimationDelegate() override = default;
private:
void OnHoldingSpaceModelAttached(HoldingSpaceModel* model) override {
model_ = model;
model_observation_.Observe(model_.get());
UpdateAnimations(false);
}
void OnHoldingSpaceModelDetached(HoldingSpaceModel* model) override {
model_ = nullptr;
model_observation_.Reset();
UpdateAnimations(false);
}
void OnHoldingSpaceItemsAdded(
const std::vector<const HoldingSpaceItem*>& items) override {
UpdateAnimations(false);
}
void OnHoldingSpaceItemsRemoved(
const std::vector<const HoldingSpaceItem*>& items) override {
const bool removed_in_progress_item =
std::ranges::any_of(items, [](const HoldingSpaceItem* item) {
return item->IsInitialized() && !item->progress().IsComplete();
});
if (removed_in_progress_item)
UpdateAnimations(true);
}
void OnHoldingSpaceItemInitialized(const HoldingSpaceItem* item) override {
UpdateAnimations(false);
}
void OnHoldingSpaceItemUpdated(
const HoldingSpaceItem* item,
const HoldingSpaceItemUpdatedFields& updated_fields) override {
if (!updated_fields.previous_progress) {
return;
}
if (item->progress().IsComplete()) {
EnsureRingAnimationOfTypeForKey(AsAnimationKey(item),
ProgressRingAnimation::Type::kPulse);
}
UpdateAnimations(false);
}
void EraseRingAnimationIfNotOfTypeForKey(AnimationKey key,
ProgressRingAnimation::Type type) {
auto* ring_animation = registry_->GetProgressRingAnimationForKey(key);
if (ring_animation && ring_animation->type() != type)
registry_->SetProgressRingAnimationForKey(key, nullptr);
}
void EnsureIconAnimationForKey(AnimationKey key) {
if (registry_->GetProgressIconAnimationForKey(key))
return;
auto* animation = registry_->SetProgressIconAnimationForKey(
key, ProgressIconAnimation::Create());
if (key == AsAnimationKey(controller_)) {
animation->Start();
}
}
void EnsureRingAnimationOfTypeForKey(AnimationKey key,
ProgressRingAnimation::Type type) {
auto* ring_animation = registry_->GetProgressRingAnimationForKey(key);
if (ring_animation && ring_animation->type() == type)
return;
auto animation = ProgressRingAnimation::CreateOfType(type);
animation->AddUnsafeAnimationUpdatedCallback(base::BindRepeating(
&ProgressIndicatorAnimationDelegate::OnRingAnimationUpdatedForKey,
base::Unretained(this), key, animation.get()));
registry_->SetProgressRingAnimationForKey(key, std::move(animation))
->Start();
}
void UpdateAnimations(bool for_removal) {
if (model_ == nullptr) {
cumulative_progress_ = HoldingSpaceProgress();
registry_->EraseAllAnimations();
return;
}
const auto controller_key = AsAnimationKey(controller_);
registry_->EraseAllAnimationsForKeyIf([&](AnimationKey key) {
return key != controller_key &&
!base::Contains(model_->items(), key,
[](const std::unique_ptr<HoldingSpaceItem>& item) {
return AsAnimationKey(item.get());
});
});
HoldingSpaceProgress last_cumulative_progress = cumulative_progress_;
cumulative_progress_ = HoldingSpaceProgress();
for (const auto& item : model_->items()) {
const auto item_key = AsAnimationKey(item.get());
if (!item->IsInitialized() || item->progress().IsHidden()) {
registry_->EraseAllAnimationsForKey(item_key);
continue;
}
if (item->progress().IsComplete()) {
registry_->SetProgressIconAnimationForKey(item_key, nullptr);
EraseRingAnimationIfNotOfTypeForKey(
item_key, ProgressRingAnimation::Type::kPulse);
continue;
}
cumulative_progress_ += item->progress();
EnsureIconAnimationForKey(item_key);
if (item->progress().IsIndeterminate()) {
EnsureRingAnimationOfTypeForKey(
item_key, ProgressRingAnimation::Type::kIndeterminate);
continue;
}
registry_->SetProgressRingAnimationForKey(item_key, nullptr);
}
if (cumulative_progress_.IsComplete()) {
registry_->SetProgressIconAnimationForKey(controller_key, nullptr);
if (!last_cumulative_progress.IsComplete()) {
if (for_removal) {
registry_->SetProgressRingAnimationForKey(controller_key, nullptr);
} else {
EnsureRingAnimationOfTypeForKey(controller_key,
ProgressRingAnimation::Type::kPulse);
}
} else {
EraseRingAnimationIfNotOfTypeForKey(
controller_key, ProgressRingAnimation::Type::kPulse);
}
return;
}
EnsureIconAnimationForKey(controller_key);
if (cumulative_progress_.IsIndeterminate()) {
EnsureRingAnimationOfTypeForKey(
controller_key, ProgressRingAnimation::Type::kIndeterminate);
return;
}
registry_->SetProgressRingAnimationForKey(controller_key, nullptr);
}
void OnRingAnimationUpdatedForKey(AnimationKey key,
ProgressRingAnimation* animation) {
if (animation->IsAnimating())
return;
base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE,
base::BindOnce(
[](const base::WeakPtr<ProgressIndicatorAnimationDelegate>& self,
AnimationKey key,
MayBeDangling<ProgressRingAnimation> animation) {
if (!self) {
return;
}
auto* registry = self->registry_.get();
if (registry->GetProgressRingAnimationForKey(key) == animation) {
registry->SetProgressRingAnimationForKey(key, nullptr);
}
},
weak_factory_.GetWeakPtr(), key, base::UnsafeDangling(animation)));
}
const raw_ptr<ProgressIndicatorAnimationRegistry, LeakedDanglingUntriaged>
registry_;
const raw_ptr<HoldingSpaceController, LeakedDanglingUntriaged> controller_;
raw_ptr<HoldingSpaceModel, LeakedDanglingUntriaged> model_ = nullptr;
HoldingSpaceProgress cumulative_progress_;
base::ScopedObservation<HoldingSpaceController,
HoldingSpaceControllerObserver>
controller_observation_{this};
base::ScopedObservation<HoldingSpaceModel, HoldingSpaceModelObserver>
model_observation_{this};
base::WeakPtrFactory<ProgressIndicatorAnimationDelegate> weak_factory_{this};
};
HoldingSpaceAnimationRegistry::HoldingSpaceAnimationRegistry() {
progress_indicator_animation_delegate_ =
std::make_unique<ProgressIndicatorAnimationDelegate>(
this, HoldingSpaceController::Get());
shell_observation_.Observe(Shell::Get());
}
HoldingSpaceAnimationRegistry::~HoldingSpaceAnimationRegistry() = default;
HoldingSpaceAnimationRegistry* HoldingSpaceAnimationRegistry::GetInstance() {
auto& instance_owner = GetInstanceOwner();
if (!instance_owner.get() && Shell::HasInstance())
instance_owner.reset(new HoldingSpaceAnimationRegistry());
return instance_owner.get();
}
void HoldingSpaceAnimationRegistry::OnShellDestroying() {
auto& instance_owner = GetInstanceOwner();
DCHECK_EQ(instance_owner.get(), this);
instance_owner.reset();
}
}