blob: 4a7218ca626db225af2f51e1574b67adf87ecbef [file] [log] [blame]
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "components/exo/wayland/zaura_shell.h"
#include <memory>
#include "ash/session/session_controller_impl.h"
#include "ash/shell.h"
#include "ash/wm/desks/desks_util.h"
#include "ash/wm/window_util.h"
#include "base/time/time.h"
#include "components/exo/test/exo_test_base.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/aura/window_occlusion_tracker.h"
#include "ui/compositor/layer_animation_observer.h"
#include "ui/compositor/layer_animation_sequence.h"
#include "ui/compositor/layer_animator.h"
#include "ui/compositor/scoped_animation_duration_scale_mode.h"
#include "ui/compositor/scoped_layer_animation_settings.h"
#include "ui/compositor/test/layer_animator_test_controller.h"
#include "ui/views/widget/widget.h"
#include "ui/wm/core/window_util.h"
#include "ui/wm/public/activation_change_observer.h"
#include "ui/wm/public/activation_client.h"
namespace exo {
namespace wayland {
namespace {
constexpr auto kTransitionDuration = base::TimeDelta::FromSeconds(3);
class TestAuraSurface : public AuraSurface {
public:
explicit TestAuraSurface(Surface* surface)
: AuraSurface(surface, /*resource=*/nullptr) {}
float last_sent_occlusion_fraction() const {
return last_sent_occlusion_fraction_;
}
int num_occlusion_updates() const { return num_occlusion_updates_; }
protected:
void SendOcclusionFraction(float occlusion_fraction) override {
last_sent_occlusion_fraction_ = occlusion_fraction;
num_occlusion_updates_++;
}
private:
float last_sent_occlusion_fraction_ = -1.0f;
int num_occlusion_updates_ = 0;
DISALLOW_COPY_AND_ASSIGN(TestAuraSurface);
};
} // namespace
class ZAuraSurfaceTest : public test::ExoTestBase,
public ::wm::ActivationChangeObserver {
public:
ZAuraSurfaceTest() {}
~ZAuraSurfaceTest() override {}
// test::ExoTestBase overrides:
void SetUp() override {
test::ExoTestBase::SetUp();
surface_.reset(new Surface);
aura_surface_.reset(new TestAuraSurface(surface_.get()));
gfx::Transform transform;
transform.Scale(1.5f, 1.5f);
parent_widget_ = CreateTestWidget();
parent_widget_->SetBounds(gfx::Rect(0, 0, 10, 10));
parent_widget_->GetNativeWindow()->SetTransform(transform);
parent_widget_->GetNativeWindow()->AddChild(surface_->window());
parent_widget_->Show();
surface_->window()->SetBounds(gfx::Rect(5, 5, 10, 10));
surface_->window()->Show();
ash::Shell::Get()->activation_client()->AddObserver(this);
aura_surface_->SetOcclusionTracking(true);
}
void TearDown() override {
ash::Shell::Get()->activation_client()->RemoveObserver(this);
parent_widget_.reset();
aura_surface_.reset();
surface_.reset();
test::ExoTestBase::TearDown();
}
// ::wm::ActivationChangeObserver overrides:
void OnWindowActivated(ActivationReason reason,
aura::Window* gained_active,
aura::Window* lost_active) override {
if (lost_active == parent_widget_->GetNativeWindow()) {
occlusion_fraction_on_activation_loss_ =
aura_surface().last_sent_occlusion_fraction();
}
}
protected:
TestAuraSurface& aura_surface() { return *aura_surface_; }
Surface& surface() { return *surface_; }
views::Widget& parent_widget() { return *parent_widget_; }
float occlusion_fraction_on_activation_loss() const {
return occlusion_fraction_on_activation_loss_;
}
std::unique_ptr<views::Widget> CreateOpaqueWidget(const gfx::Rect& bounds) {
return CreateTestWidget(
/*delegate=*/nullptr,
/*container_id=*/ash::desks_util::GetActiveDeskContainerId(), bounds,
/*show=*/false);
}
private:
std::unique_ptr<TestAuraSurface> aura_surface_;
std::unique_ptr<Surface> surface_;
std::unique_ptr<views::Widget> parent_widget_;
float occlusion_fraction_on_activation_loss_ = -1.0f;
DISALLOW_COPY_AND_ASSIGN(ZAuraSurfaceTest);
};
TEST_F(ZAuraSurfaceTest,
LosingActivationWithNoAnimatingWindowsSendsCorrectOcclusionFraction) {
EXPECT_EQ(0.0f, aura_surface().last_sent_occlusion_fraction());
EXPECT_EQ(1, aura_surface().num_occlusion_updates());
::wm::ActivateWindow(parent_widget().GetNativeWindow());
// Creating an opaque window but don't show it.
auto widget = CreateOpaqueWidget(gfx::Rect(0, 0, 10, 10));
// Occlusion sent before de-activation should include that widget.
widget->Show();
EXPECT_EQ(0.2f, occlusion_fraction_on_activation_loss());
EXPECT_EQ(0.2f, aura_surface().last_sent_occlusion_fraction());
EXPECT_EQ(2, aura_surface().num_occlusion_updates());
}
TEST_F(ZAuraSurfaceTest,
LosingActivationWithAnimatingWindowsSendsTargetOcclusionFraction) {
EXPECT_EQ(0.0f, aura_surface().last_sent_occlusion_fraction());
EXPECT_EQ(1, aura_surface().num_occlusion_updates());
::wm::ActivateWindow(parent_widget().GetNativeWindow());
// Creating an opaque window but don't show it.
auto widget = CreateOpaqueWidget(gfx::Rect(0, 0, 10, 10));
widget->GetNativeWindow()->layer()->SetOpacity(0.0f);
ui::ScopedAnimationDurationScaleMode scoped_animation_duration_scale_mode(
ui::ScopedAnimationDurationScaleMode::NORMAL_DURATION);
ui::LayerAnimatorTestController test_controller(
ui::LayerAnimator::CreateImplicitAnimator());
ui::ScopedLayerAnimationSettings layer_animation_settings(
test_controller.animator());
layer_animation_settings.SetTransitionDuration(kTransitionDuration);
widget->GetNativeWindow()->layer()->SetAnimator(test_controller.animator());
widget->GetNativeWindow()->layer()->SetOpacity(1.0f);
// Opacity animation uses threaded animation.
test_controller.StartThreadedAnimationsIfNeeded();
test_controller.Step(kTransitionDuration / 3);
// No occlusion updates should happen until the window is de-activated.
EXPECT_EQ(1, aura_surface().num_occlusion_updates());
// Occlusion sent before de-activation should include the window animating
// to be completely opaque.
widget->Show();
EXPECT_EQ(0.2f, occlusion_fraction_on_activation_loss());
EXPECT_EQ(0.2f, aura_surface().last_sent_occlusion_fraction());
EXPECT_EQ(2, aura_surface().num_occlusion_updates());
// Explicitly stop animation because threaded animation may have started
// a bit later. |kTransitionDuration| may not be quite enough to reach the
// end.
test_controller.Step(kTransitionDuration / 3);
test_controller.Step(kTransitionDuration / 3);
widget->GetNativeWindow()->layer()->GetAnimator()->StopAnimating();
widget->GetNativeWindow()->layer()->SetAnimator(nullptr);
// Expect the occlusion tracker to send an update after the animation
// finishes.
EXPECT_EQ(0.2f, aura_surface().last_sent_occlusion_fraction());
EXPECT_EQ(3, aura_surface().num_occlusion_updates());
}
TEST_F(ZAuraSurfaceTest,
LosingActivationByTriggeringTheLockScreenDoesNotSendOccludedFraction) {
EXPECT_EQ(0.0f, aura_surface().last_sent_occlusion_fraction());
EXPECT_EQ(1, aura_surface().num_occlusion_updates());
::wm::ActivateWindow(parent_widget().GetNativeWindow());
// Lock the screen.
views::Widget::InitParams params(views::Widget::InitParams::TYPE_WINDOW);
auto lock_widget = std::make_unique<views::Widget>();
params.ownership = views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
params.context = GetContext();
params.bounds = gfx::Rect(0, 0, 100, 100);
lock_widget->Init(std::move(params));
ash::Shell::GetContainer(ash::Shell::GetPrimaryRootWindow(),
ash::kShellWindowId_LockScreenContainer)
->AddChild(lock_widget->GetNativeView());
// Simulate real screen locker to change session state to LOCKED
// when it is shown.
auto* controller = ash::Shell::Get()->session_controller();
GetSessionControllerClient()->LockScreen();
lock_widget->Show();
EXPECT_TRUE(controller->IsScreenLocked());
EXPECT_TRUE(lock_widget->GetNativeView()->HasFocus());
// We should have lost focus, but not reported that the window has been
// fully occluded.
EXPECT_NE(parent_widget().GetNativeWindow(),
ash::window_util::GetActiveWindow());
EXPECT_EQ(0.0f, occlusion_fraction_on_activation_loss());
EXPECT_EQ(0.0f, aura_surface().last_sent_occlusion_fraction());
}
TEST_F(ZAuraSurfaceTest, OcclusionIncludesOffScreenArea) {
UpdateDisplay("150x150");
// This is scaled by 1.5 - set the bounds to (-60, 75, 120, 150) in screen
// coordinates so 75% of it is outside of the 100x100 screen.
surface().window()->SetBounds(gfx::Rect(-40, 50, 80, 100));
surface().OnWindowOcclusionChanged();
EXPECT_EQ(0.75f, aura_surface().last_sent_occlusion_fraction());
}
TEST_F(ZAuraSurfaceTest, ZeroSizeWindowSendsZeroOcclusionFraction) {
// Zero sized window should not be occluded.
surface().window()->SetBounds(gfx::Rect(0, 0, 0, 0));
surface().OnWindowOcclusionChanged();
EXPECT_EQ(0.0f, aura_surface().last_sent_occlusion_fraction());
}
} // namespace wayland
} // namespace exo