#include "ash/rotator/screen_rotation_animator.h"
#include <memory>
#include <utility>
#include "ash/public/cpp/metrics_util.h"
#include "ash/public/cpp/shell_window_ids.h"
#include "ash/rotator/screen_rotation_animation.h"
#include "ash/rotator/screen_rotation_animator_observer.h"
#include "ash/shell.h"
#include "ash/utility/layer_util.h"
#include "ash/utility/transformer_util.h"
#include "base/functional/bind.h"
#include "base/metrics/histogram_macros.h"
#include "base/task/sequenced_task_runner.h"
#include "base/time/time.h"
#include "components/viz/common/frame_sinks/copy_output_request.h"
#include "components/viz/common/frame_sinks/copy_output_result.h"
#include "ui/aura/window.h"
#include "ui/compositor/animation_throughput_reporter.h"
#include "ui/compositor/callback_layer_animation_observer.h"
#include "ui/compositor/compositor.h"
#include "ui/compositor/layer.h"
#include "ui/compositor/layer_animation_sequence.h"
#include "ui/compositor/layer_animator.h"
#include "ui/compositor/layer_owner.h"
#include "ui/compositor/layer_tree_owner.h"
#include "ui/compositor/layer_type.h"
#include "ui/display/display.h"
#include "ui/display/manager/display_manager.h"
#include "ui/display/manager/managed_display_info.h"
#include "ui/display/screen.h"
#include "ui/gfx/animation/tween.h"
#include "ui/gfx/geometry/point.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/gfx/geometry/size_f.h"
#include "ui/gfx/geometry/transform.h"
#include "ui/gfx/scoped_animation_duration_scale_mode.h"
#include "ui/wm/core/window_util.h"
namespace ash {
namespace {
const int kRotationDegrees = 20;
const int kRotationDurationInMs = 250;
const int kCounterClockWiseRotationFactor = 1;
const int kClockWiseRotationFactor = -1;
constexpr char kRotationAnimationSmoothness[] =
"Ash.Rotation.AnimationSmoothness";
display::Display::Rotation GetCurrentScreenRotation(int64_t display_id) {
return Shell::Get()
->display_manager()
->GetDisplayInfo(display_id)
.GetActiveRotation();
}
int GetRotationFactor(display::Display::Rotation initial_rotation,
display::Display::Rotation new_rotation) {
return (initial_rotation + 3) % 4 == new_rotation
? kCounterClockWiseRotationFactor
: kClockWiseRotationFactor;
}
aura::Window* GetScreenRotationContainer(aura::Window* root_window) {
return root_window->GetChildById(kShellWindowId_ScreenAnimationContainer);
}
bool Is180DegreeFlip(display::Display::Rotation initial_rotation,
display::Display::Rotation new_rotation) {
return (initial_rotation + 2) % 4 == new_rotation;
}
int GetInitialDegrees(display::Display::Rotation initial_rotation,
display::Display::Rotation new_rotation) {
return (Is180DegreeFlip(initial_rotation, new_rotation) ? 180 : 90);
}
void AddLayerAboveWindowLayer(aura::Window* root_window,
ui::Layer* container_layer,
ui::Layer* layer) {
DCHECK_EQ(container_layer->parent(), root_window->layer());
root_window->layer()->Add(layer);
root_window->layer()->StackAbove(layer, container_layer);
}
void AddLayerBelowWindowLayer(aura::Window* root_window,
ui::Layer* top_layer,
ui::Layer* layer) {
root_window->layer()->Add(layer);
root_window->layer()->StackBelow(layer, top_layer);
}
bool AnimationEndedCallback(
base::WeakPtr<ScreenRotationAnimator> animator,
const ui::CallbackLayerAnimationObserver& observer) {
if (animator)
animator->ProcessAnimationQueue();
return true;
}
gfx::Transform CreateScreenRotationOldLayerTransformForDisplay(
display::Display::Rotation old_rotation,
display::Display::Rotation new_rotation,
const display::Display& display) {
gfx::Transform inverse;
CHECK(CreateRotationTransform(old_rotation, new_rotation,
gfx::SizeF(display.size()))
.GetInverse(&inverse));
return inverse;
}
bool IgnoreCopyResult(int64_t request_id, int64_t current_request_id) {
DCHECK(request_id <= current_request_id);
return request_id < current_request_id;
}
bool RootWindowChangedForDisplayId(aura::Window* root_window,
int64_t display_id) {
return root_window != Shell::GetRootWindowForDisplayId(display_id);
}
std::unique_ptr<ui::LayerTreeOwner> CreateMaskLayerTreeOwner(
const gfx::Rect& rect) {
std::unique_ptr<ui::Layer> mask_layer =
std::make_unique<ui::Layer>(ui::LAYER_SOLID_COLOR);
mask_layer->SetBounds(rect);
mask_layer->SetColor(SK_ColorBLACK);
return std::make_unique<ui::LayerTreeOwner>(std::move(mask_layer));
}
}
ScreenRotationAnimator::ScreenRotationAnimator(aura::Window* root_window)
: root_window_(root_window),
screen_rotation_state_(IDLE),
rotation_request_id_(0),
disable_animation_timers_for_test_(false) {}
ScreenRotationAnimator::~ScreenRotationAnimator() {
weak_factory_.InvalidateWeakPtrs();
}
void ScreenRotationAnimator::StartRotationAnimation(
std::unique_ptr<ScreenRotationRequest> rotation_request) {
const display::Display::Rotation current_rotation =
GetCurrentScreenRotation(rotation_request->display_id);
if (current_rotation == rotation_request->new_rotation) {
ProcessAnimationQueue();
return;
}
rotation_request->old_rotation = current_rotation;
if (DisplayConfigurationController::ANIMATION_SYNC ==
rotation_request->mode) {
StartSlowAnimation(std::move(rotation_request));
} else {
current_async_rotation_request_ = ScreenRotationRequest(*rotation_request);
RequestCopyScreenRotationContainerLayer(
std::make_unique<viz::CopyOutputRequest>(
viz::CopyOutputRequest::ResultFormat::RGBA,
viz::CopyOutputRequest::ResultDestination::kSharedImage,
CreateAfterCopyCallbackBeforeRotation(
std::move(rotation_request))));
screen_rotation_state_ = COPY_REQUESTED;
}
}
void ScreenRotationAnimator::StartSlowAnimation(
std::unique_ptr<ScreenRotationRequest> rotation_request) {
CreateOldLayerTreeForSlowAnimation();
auto weak_ptr = weak_factory_.GetWeakPtr();
SetRotation(rotation_request->display_id, rotation_request->old_rotation,
rotation_request->new_rotation, rotation_request->source);
if (!weak_ptr) {
return;
}
AnimateRotation(std::move(rotation_request));
}
void ScreenRotationAnimator::SetRotation(
int64_t display_id,
display::Display::Rotation old_rotation,
display::Display::Rotation new_rotation,
display::Display::RotationSource source) {
current_async_rotation_request_.reset();
ui::Compositor* compositor = root_window_->layer()->GetCompositor();
compositor->SetAllowLocksToExtendTimeout(true);
Shell::Get()->display_manager()->SetDisplayRotation(display_id, new_rotation,
source);
compositor->SetAllowLocksToExtendTimeout(false);
const display::Display display =
Shell::Get()->display_manager()->GetDisplayForId(display_id);
old_layer_tree_owner_->root()->SetTransform(
CreateScreenRotationOldLayerTransformForDisplay(old_rotation,
new_rotation, display));
}
void ScreenRotationAnimator::RequestCopyScreenRotationContainerLayer(
std::unique_ptr<viz::CopyOutputRequest> copy_output_request) {
ui::Layer* screen_rotation_container_layer =
GetScreenRotationContainer(root_window_)->layer();
copy_output_request->set_area(
gfx::Rect(screen_rotation_container_layer->size()));
copy_output_request->set_result_task_runner(
base::SequencedTaskRunner::GetCurrentDefault());
screen_rotation_container_layer->RequestCopyOfOutput(
std::move(copy_output_request));
}
ScreenRotationAnimator::CopyCallback
ScreenRotationAnimator::CreateAfterCopyCallbackBeforeRotation(
std::unique_ptr<ScreenRotationRequest> rotation_request) {
return base::BindOnce(&ScreenRotationAnimator::
OnScreenRotationContainerLayerCopiedBeforeRotation,
weak_factory_.GetWeakPtr(),
std::move(rotation_request));
}
ScreenRotationAnimator::CopyCallback
ScreenRotationAnimator::CreateAfterCopyCallbackAfterRotation(
std::unique_ptr<ScreenRotationRequest> rotation_request) {
return base::BindOnce(&ScreenRotationAnimator::
OnScreenRotationContainerLayerCopiedAfterRotation,
weak_factory_.GetWeakPtr(),
std::move(rotation_request));
}
void ScreenRotationAnimator::OnScreenRotationContainerLayerCopiedBeforeRotation(
std::unique_ptr<ScreenRotationRequest> rotation_request,
std::unique_ptr<viz::CopyOutputResult> result) {
if (IgnoreCopyResult(rotation_request->id, rotation_request_id_))
return;
if (RootWindowChangedForDisplayId(root_window_,
rotation_request->display_id)) {
ProcessAnimationQueue();
return;
}
if (result->IsEmpty()) {
Shell::Get()->display_manager()->SetDisplayRotation(
rotation_request->display_id, rotation_request->new_rotation,
rotation_request->source);
ProcessAnimationQueue();
return;
}
old_layer_tree_owner_ = CopyLayerTree(std::move(result));
AddLayerAboveWindowLayer(root_window_,
GetScreenRotationContainer(root_window_)->layer(),
old_layer_tree_owner_->root());
animation_scale_mode_ =
std::make_unique<gfx::ScopedAnimationDurationScaleMode>(
gfx::ScopedAnimationDurationScaleMode::ZERO_DURATION);
for (auto& observer : screen_rotation_animator_observers_)
observer.OnScreenCopiedBeforeRotation();
auto weak_ptr = weak_factory_.GetWeakPtr();
SetRotation(rotation_request->display_id, rotation_request->old_rotation,
rotation_request->new_rotation, rotation_request->source);
if (!weak_ptr) {
return;
}
RequestCopyScreenRotationContainerLayer(
std::make_unique<viz::CopyOutputRequest>(
viz::CopyOutputRequest::ResultFormat::RGBA,
viz::CopyOutputRequest::ResultDestination::kSharedImage,
CreateAfterCopyCallbackAfterRotation(std::move(rotation_request))));
}
void ScreenRotationAnimator::OnScreenRotationContainerLayerCopiedAfterRotation(
std::unique_ptr<ScreenRotationRequest> rotation_request,
std::unique_ptr<viz::CopyOutputResult> result) {
animation_scale_mode_.reset();
if (IgnoreCopyResult(rotation_request->id, rotation_request_id_)) {
NotifyAnimationFinished(true);
return;
}
if (RootWindowChangedForDisplayId(root_window_,
rotation_request->display_id) ||
result->IsEmpty()) {
ProcessAnimationQueue();
return;
}
new_layer_tree_owner_ = CopyLayerTree(std::move(result));
AddLayerBelowWindowLayer(root_window_, old_layer_tree_owner_->root(),
new_layer_tree_owner_->root());
AnimateRotation(std::move(rotation_request));
}
void ScreenRotationAnimator::CreateOldLayerTreeForSlowAnimation() {
old_layer_tree_owner_ = ::wm::RecreateLayers(root_window_);
AddLayerAboveWindowLayer(root_window_,
GetScreenRotationContainer(root_window_)->layer(),
old_layer_tree_owner_->root());
}
std::unique_ptr<ui::LayerTreeOwner> ScreenRotationAnimator::CopyLayerTree(
std::unique_ptr<viz::CopyOutputResult> result) {
gfx::Size layer_size =
GetScreenRotationContainer(root_window_)->layer()->size();
std::unique_ptr<ui::Layer> copy_layer =
CreateLayerFromCopyOutputResult(std::move(result), layer_size);
CHECK_EQ(copy_layer->type(), ui::LAYER_SOLID_COLOR);
DCHECK_EQ(copy_layer->size(),
GetScreenRotationContainer(root_window_)->layer()->size());
return std::make_unique<ui::LayerTreeOwner>(std::move(copy_layer));
}
void ScreenRotationAnimator::AnimateRotation(
std::unique_ptr<ScreenRotationRequest> rotation_request) {
screen_rotation_state_ = ROTATING;
const int rotation_factor = GetRotationFactor(rotation_request->old_rotation,
rotation_request->new_rotation);
const int old_layer_initial_rotation_degrees = GetInitialDegrees(
rotation_request->old_rotation, rotation_request->new_rotation);
const base::TimeDelta duration = base::Milliseconds(kRotationDurationInMs);
const gfx::Tween::Type tween_type = gfx::Tween::FAST_OUT_LINEAR_IN;
const gfx::Rect rotated_screen_bounds = root_window_->GetTargetBounds();
const gfx::Point pivot = gfx::Point(rotated_screen_bounds.width() / 2,
rotated_screen_bounds.height() / 2);
ui::Layer* screen_rotation_container_layer =
GetScreenRotationContainer(root_window_)->layer();
ui::Layer* new_root_layer;
if (!new_layer_tree_owner_) {
new_root_layer = screen_rotation_container_layer;
} else {
new_root_layer = new_layer_tree_owner_->root();
mask_layer_tree_owner_ = CreateMaskLayerTreeOwner(
gfx::Rect(screen_rotation_container_layer->size()));
AddLayerBelowWindowLayer(root_window_, new_root_layer,
mask_layer_tree_owner_->root());
}
std::unique_ptr<ScreenRotationAnimation> new_layer_screen_rotation =
std::make_unique<ScreenRotationAnimation>(
new_root_layer, kRotationDegrees * rotation_factor,
0 , new_root_layer->opacity(),
new_root_layer->opacity() , pivot, duration,
tween_type);
ui::LayerAnimator* new_layer_animator = new_root_layer->GetAnimator();
new_layer_animator->set_preemption_strategy(
ui::LayerAnimator::REPLACE_QUEUED_ANIMATIONS);
std::unique_ptr<ui::LayerAnimationSequence> new_layer_animation_sequence =
std::make_unique<ui::LayerAnimationSequence>(
std::move(new_layer_screen_rotation));
ui::Layer* old_root_layer = old_layer_tree_owner_->root();
const gfx::Rect original_screen_bounds = old_root_layer->GetTargetBounds();
gfx::Transform translate_transform;
translate_transform.Translate(
(rotated_screen_bounds.width() - original_screen_bounds.width()) / 2,
(rotated_screen_bounds.height() - original_screen_bounds.height()) / 2);
old_root_layer->SetTransform(translate_transform);
std::unique_ptr<ScreenRotationAnimation> old_layer_screen_rotation =
std::make_unique<ScreenRotationAnimation>(
old_root_layer, old_layer_initial_rotation_degrees * rotation_factor,
(old_layer_initial_rotation_degrees - kRotationDegrees) *
rotation_factor,
old_root_layer->opacity(), 0.0f , pivot, duration,
tween_type);
ui::LayerAnimator* old_layer_animator = old_root_layer->GetAnimator();
old_layer_animator->set_preemption_strategy(
ui::LayerAnimator::REPLACE_QUEUED_ANIMATIONS);
std::unique_ptr<ui::LayerAnimationSequence> old_layer_animation_sequence =
std::make_unique<ui::LayerAnimationSequence>(
std::move(old_layer_screen_rotation));
if (disable_animation_timers_for_test_) {
if (new_layer_tree_owner_)
new_layer_animator->set_disable_timer_for_test(true);
old_layer_animator->set_disable_timer_for_test(true);
}
ui::AnimationThroughputReporter reporter(
old_layer_animator,
metrics_util::ForSmoothnessV3(base::BindRepeating([](int smoothness) {
UMA_HISTOGRAM_PERCENTAGE(kRotationAnimationSmoothness, smoothness);
})));
ui::CallbackLayerAnimationObserver* observer =
new ui::CallbackLayerAnimationObserver(base::BindRepeating(
&AnimationEndedCallback, weak_factory_.GetWeakPtr()));
if (new_layer_tree_owner_)
new_layer_animation_sequence->AddObserver(observer);
new_layer_animator->StartAnimation(new_layer_animation_sequence.release());
old_layer_animation_sequence->AddObserver(observer);
old_layer_animator->StartAnimation(old_layer_animation_sequence.release());
observer->SetActive();
}
void ScreenRotationAnimator::Rotate(
display::Display::Rotation new_rotation,
display::Display::RotationSource source,
DisplayConfigurationController::RotationAnimation mode) {
rotation_request_id_++;
const int64_t display_id =
display::Screen::Get()->GetDisplayNearestWindow(root_window_).id();
std::unique_ptr<ScreenRotationRequest> rotation_request =
std::make_unique<ScreenRotationRequest>(rotation_request_id_, display_id,
new_rotation, source, mode);
target_rotation_ = new_rotation;
if (mode == DisplayConfigurationController::ANIMATION_SYNC)
current_async_rotation_request_.reset();
switch (screen_rotation_state_) {
case IDLE:
DCHECK(!current_async_rotation_request_);
[[fallthrough]];
case COPY_REQUESTED:
if (current_async_rotation_request_ &&
!RootWindowChangedForDisplayId(
root_window_, current_async_rotation_request_->display_id)) {
Shell::Get()->display_manager()->SetDisplayRotation(
current_async_rotation_request_->display_id,
current_async_rotation_request_->new_rotation,
current_async_rotation_request_->source);
current_async_rotation_request_.reset();
}
StartRotationAnimation(std::move(rotation_request));
break;
case ROTATING:
last_pending_request_ = std::move(rotation_request);
StopAnimating();
break;
}
}
void ScreenRotationAnimator::AddObserver(
ScreenRotationAnimatorObserver* observer) {
screen_rotation_animator_observers_.AddObserver(observer);
}
void ScreenRotationAnimator::RemoveObserver(
ScreenRotationAnimatorObserver* observer) {
screen_rotation_animator_observers_.RemoveObserver(observer);
}
void ScreenRotationAnimator::ProcessAnimationQueue() {
screen_rotation_state_ = IDLE;
old_layer_tree_owner_.reset();
new_layer_tree_owner_.reset();
mask_layer_tree_owner_.reset();
current_async_rotation_request_.reset();
if (last_pending_request_ &&
!RootWindowChangedForDisplayId(root_window_,
last_pending_request_->display_id)) {
StartRotationAnimation(std::move(last_pending_request_));
return;
}
NotifyAnimationFinished(false);
}
bool ScreenRotationAnimator::IsRotating() const {
return screen_rotation_state_ != IDLE;
}
display::Display::Rotation ScreenRotationAnimator::GetTargetRotation() const {
return target_rotation_;
}
void ScreenRotationAnimator::StopAnimating() {
if (old_layer_tree_owner_)
old_layer_tree_owner_->root()->GetAnimator()->StopAnimating();
if (new_layer_tree_owner_)
new_layer_tree_owner_->root()->GetAnimator()->StopAnimating();
mask_layer_tree_owner_.reset();
}
void ScreenRotationAnimator::NotifyAnimationFinished(bool canceled) {
for (auto& observer : screen_rotation_animator_observers_)
observer.OnScreenRotationAnimationFinished(this, canceled);
}
}