blob: 969f37dc7247b435a1065dd6a878f741d5df722d [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 "ash/home_screen/home_screen_controller.h"
#include <memory>
#include <vector>
#include "ash/app_list/app_list_controller_impl.h"
#include "ash/home_screen/home_launcher_gesture_handler.h"
#include "ash/home_screen/home_screen_delegate.h"
#include "ash/home_screen/window_scale_animation.h"
#include "ash/public/cpp/ash_features.h"
#include "ash/public/cpp/fps_counter.h"
#include "ash/public/cpp/shell_window_ids.h"
#include "ash/public/cpp/window_properties.h"
#include "ash/scoped_animation_disabler.h"
#include "ash/session/session_controller_impl.h"
#include "ash/shelf/shelf.h"
#include "ash/shell.h"
#include "ash/wallpaper/wallpaper_controller_impl.h"
#include "ash/wm/mru_window_tracker.h"
#include "ash/wm/overview/overview_controller.h"
#include "ash/wm/splitview/split_view_controller.h"
#include "ash/wm/tablet_mode/tablet_mode_controller.h"
#include "ash/wm/window_state.h"
#include "ash/wm/window_transient_descendant_iterator.h"
#include "ash/wm/window_util.h"
#include "base/barrier_closure.h"
#include "base/logging.h"
#include "base/metrics/histogram_macros.h"
#include "base/stl_util.h"
#include "ui/aura/window.h"
#include "ui/compositor/layer_animation_observer.h"
#include "ui/display/manager/display_manager.h"
#include "ui/wm/core/window_animations.h"
namespace ash {
namespace {
constexpr char kHomescreenAnimationHistogram[] =
"Ash.Homescreen.AnimationSmoothness";
// Minimizes all windows in |windows| that aren't in the home screen container,
// and are not in |windows_to_ignore|. Done in reverse order to preserve the mru
// ordering.
// Returns true if any windows are minimized.
bool MinimizeAllWindows(const aura::Window::Windows& windows,
const aura::Window::Windows& windows_to_ignore) {
aura::Window* container = Shell::Get()->GetPrimaryRootWindow()->GetChildById(
kShellWindowId_HomeScreenContainer);
aura::Window::Windows windows_to_minimize;
for (auto it = windows.rbegin(); it != windows.rend(); it++) {
if (!container->Contains(*it) && !base::Contains(windows_to_ignore, *it) &&
!WindowState::Get(*it)->IsMinimized()) {
windows_to_minimize.push_back(*it);
}
}
window_util::MinimizeAndHideWithoutAnimation(windows_to_minimize);
return !windows_to_minimize.empty();
}
// Layer animation observer that waits for layer animator to schedule, and
// complete animations. When all animations complete, it fires |callback| and
// deletes itself.
class WindowAnimationsCallback : public ui::LayerAnimationObserver {
public:
WindowAnimationsCallback(base::OnceClosure callback,
ui::LayerAnimator* animator)
: callback_(std::move(callback)), animator_(animator) {
animator_->AddObserver(this);
}
~WindowAnimationsCallback() override { animator_->RemoveObserver(this); }
// ui::LayerAnimationObserver:
void OnLayerAnimationEnded(ui::LayerAnimationSequence* sequence) override {
FireCallbackIfDone();
}
void OnLayerAnimationAborted(ui::LayerAnimationSequence* sequence) override {
FireCallbackIfDone();
}
void OnLayerAnimationScheduled(
ui::LayerAnimationSequence* sequence) override {}
void OnDetachedFromSequence(ui::LayerAnimationSequence* sequence) override {
FireCallbackIfDone();
}
private:
// Fires the callback if all scheduled animations completed (either ended or
// got aborted).
void FireCallbackIfDone() {
if (!callback_ || animator_->is_animating())
return;
std::move(callback_).Run();
delete this;
}
base::OnceClosure callback_;
ui::LayerAnimator* animator_;
};
} // namespace
HomeScreenController::HomeScreenController()
: home_launcher_gesture_handler_(
std::make_unique<HomeLauncherGestureHandler>()) {
Shell::Get()->overview_controller()->AddObserver(this);
Shell::Get()->wallpaper_controller()->AddObserver(this);
}
HomeScreenController::~HomeScreenController() {
Shell::Get()->wallpaper_controller()->RemoveObserver(this);
Shell::Get()->overview_controller()->RemoveObserver(this);
}
void HomeScreenController::Show() {
DCHECK(Shell::Get()->tablet_mode_controller()->InTabletMode());
if (!Shell::Get()->session_controller()->IsActiveUserSessionStarted())
return;
delegate_->ShowHomeScreenView();
UpdateVisibility();
aura::Window* window = delegate_->GetHomeScreenWindow();
if (window)
Shelf::ForWindow(window)->MaybeUpdateShelfBackground();
}
bool HomeScreenController::GoHome(int64_t display_id) {
DCHECK(Shell::Get()->tablet_mode_controller()->InTabletMode());
auto* app_list_controller = Shell::Get()->app_list_controller();
if (app_list_controller->IsShowingEmbeddedAssistantUI()) {
app_list_controller->presenter()->ShowEmbeddedAssistantUI(false);
}
OverviewController* overview_controller = Shell::Get()->overview_controller();
SplitViewController* split_view_controller =
SplitViewController::Get(Shell::GetPrimaryRootWindow());
const bool split_view_active = split_view_controller->InSplitViewMode();
if (!features::IsDragFromShelfToHomeOrOverviewEnabled()) {
if (home_launcher_gesture_handler_->ShowHomeLauncher(
Shell::Get()->display_manager()->GetDisplayForId(display_id))) {
return true;
}
if (overview_controller->InOverviewSession()) {
// End overview mode.
overview_controller->EndOverview(
OverviewSession::EnterExitOverviewType::kSlideOutExit);
return true;
}
if (split_view_active) {
// End split view mode.
split_view_controller->EndSplitView(
SplitViewController::EndReason::kHomeLauncherPressed);
return true;
}
// The home screen opens for the current active desk, there's no need to
// minimize windows in the inactive desks.
if (MinimizeAllWindows(
Shell::Get()->mru_window_tracker()->BuildWindowForCycleList(
kActiveDesk),
{} /*windows_to_ignore*/)) {
return true;
}
return false;
}
// The home screen opens for the current active desk, there's no need to
// minimize windows in the inactive desks.
aura::Window::Windows windows =
Shell::Get()->mru_window_tracker()->BuildWindowForCycleList(kActiveDesk);
// The foreground window or windows (for split mode) - the windows that will
// not be minimized without animations (instead they wil bee animated into the
// home screen).
std::vector<aura::Window*> foreground_windows;
if (split_view_active) {
foreground_windows = {split_view_controller->left_window(),
split_view_controller->right_window()};
base::EraseIf(foreground_windows,
[](aura::Window* window) { return !window; });
} else if (!windows.empty() && !WindowState::Get(windows[0])->IsMinimized()) {
foreground_windows.push_back(windows[0]);
}
if (split_view_active) {
// End split view mode.
split_view_controller->EndSplitView(
SplitViewController::EndReason::kHomeLauncherPressed);
// If overview session is active (e.g. on one side of the split view), end
// it immediately, to prevent overview UI being visible while transitioning
// to home screen.
if (overview_controller->InOverviewSession()) {
overview_controller->EndOverview(
OverviewSession::EnterExitOverviewType::kImmediateExit);
}
}
// If overview is active (if overview was active in split view, it exited by
// this point), just fade it out to home screen.
if (overview_controller->InOverviewSession()) {
overview_controller->EndOverview(
OverviewSession::EnterExitOverviewType::kFadeOutExit);
return true;
}
// First minimize all inactive windows.
const bool window_minimized =
MinimizeAllWindows(windows, foreground_windows /*windows_to_ignore*/);
if (foreground_windows.empty())
return window_minimized;
{
// Disable window animations before updating home launcher target
// position. Calling OnHomeLauncherPositionChanged() can cause
// display work area update, and resulting cross-fade window bounds change
// animation can interfere with WindowTransformToHomeScreenAnimation
// visuals.
//
// TODO(https://crbug.com/1019531): This can be removed once transitions
// between in-app state and home do not cause work area updates.
std::vector<std::unique_ptr<ScopedAnimationDisabler>> animation_disablers;
for (auto* window : foreground_windows) {
animation_disablers.push_back(
std::make_unique<ScopedAnimationDisabler>(window));
}
delegate_->OnHomeLauncherPositionChanged(100 /* percent_shown */,
display_id);
}
StartTrackingAnimationSmoothness(display_id);
base::RepeatingClosure window_transforms_callback = base::BarrierClosure(
foreground_windows.size(),
base::BindOnce(&HomeScreenController::NotifyHomeLauncherTransitionEnded,
weak_ptr_factory_.GetWeakPtr(), true /*shown*/,
display_id));
// Minimize currently active windows, but this time, using animation.
// Home screen will show when all the windows are done minimizing.
for (auto* foreground_window : foreground_windows) {
if (::wm::WindowAnimationsDisabled(foreground_window)) {
WindowState::Get(foreground_window)->Minimize();
window_transforms_callback.Run();
} else {
// Create animator observer that will fire |window_transforms_callback|
// once the window layer stops animating - it deletes itself when
// animations complete.
new WindowAnimationsCallback(window_transforms_callback,
foreground_window->layer()->GetAnimator());
WindowState::Get(foreground_window)->Minimize();
}
}
return true;
}
void HomeScreenController::NotifyHomeLauncherTransitionEnded(
bool shown,
int64_t display_id) {
RecordAnimationSmoothness();
if (delegate_)
delegate_->OnHomeLauncherAnimationComplete(shown, display_id);
}
void HomeScreenController::SetDelegate(HomeScreenDelegate* delegate) {
delegate_ = delegate;
}
void HomeScreenController::OnWindowDragStarted() {
in_window_dragging_ = true;
UpdateVisibility();
// Dismiss Assistant if it's running when a window drag starts.
if (Shell::Get()->app_list_controller()->IsShowingEmbeddedAssistantUI()) {
Shell::Get()->app_list_controller()->presenter()->ShowEmbeddedAssistantUI(
false);
}
}
void HomeScreenController::OnWindowDragEnded(bool animate) {
in_window_dragging_ = false;
UpdateVisibility();
if (ShouldShowHomeScreen()) {
home_screen_presenter_.ScheduleOverviewModeAnimation(
HomeScreenPresenter::TransitionType::kScaleHomeIn, animate);
}
}
bool HomeScreenController::IsHomeScreenVisible() const {
return delegate_->IsHomeScreenVisible();
}
void HomeScreenController::StartTrackingAnimationSmoothness(
int64_t display_id) {
auto* root_window = Shell::GetRootWindowForDisplayId(display_id);
auto* compositor = root_window->layer()->GetCompositor();
fps_counter_ = std::make_unique<FpsCounter>(compositor);
}
void HomeScreenController::RecordAnimationSmoothness() {
if (!fps_counter_)
return;
int smoothness = fps_counter_->ComputeSmoothness();
if (smoothness >= 0)
UMA_HISTOGRAM_PERCENTAGE(kHomescreenAnimationHistogram, smoothness);
fps_counter_.reset();
}
void HomeScreenController::OnAppListViewShown() {
split_view_observer_.Add(
SplitViewController::Get(delegate_->GetHomeScreenWindow()));
UpdateVisibility();
}
void HomeScreenController::OnAppListViewClosing() {
split_view_observer_.RemoveAll();
}
void HomeScreenController::OnSplitViewStateChanged(
SplitViewController::State previous_state,
SplitViewController::State state) {
UpdateVisibility();
}
void HomeScreenController::OnOverviewModeStarting() {
const OverviewSession::EnterExitOverviewType overview_enter_type =
Shell::Get()
->overview_controller()
->overview_session()
->enter_exit_overview_type();
const bool animate =
IsHomeScreenVisible() &&
(overview_enter_type ==
OverviewSession::EnterExitOverviewType::kSlideInEnter ||
overview_enter_type ==
OverviewSession::EnterExitOverviewType::kFadeInEnter);
const HomeScreenPresenter::TransitionType transition =
overview_enter_type ==
OverviewSession::EnterExitOverviewType::kFadeInEnter
? HomeScreenPresenter::TransitionType::kScaleHomeOut
: HomeScreenPresenter::TransitionType::kSlideHomeOut;
home_screen_presenter_.ScheduleOverviewModeAnimation(transition, animate);
}
void HomeScreenController::OnOverviewModeEnding(
OverviewSession* overview_session) {
// The launcher will be shown after overview mode finishes animating, in
// OnOverviewModeEndingAnimationComplete(). Overview however is nullptr by
// the time the animations are finished, so cache the exit type here.
overview_exit_type_ =
base::make_optional(overview_session->enter_exit_overview_type());
// If the overview is fading out, start the home screen animation in parallel.
// Otherwise the transition will be initiated in
// OnOverviewModeEndingAnimationComplete().
if (overview_session->enter_exit_overview_type() ==
OverviewSession::EnterExitOverviewType::kFadeOutExit) {
home_screen_presenter_.ScheduleOverviewModeAnimation(
HomeScreenPresenter::TransitionType::kScaleHomeIn, true /*animate*/);
// Make sure the window visibility is updated, in case it was previously
// hidden due to overview being shown.
UpdateVisibility();
}
}
void HomeScreenController::OnOverviewModeEndingAnimationComplete(
bool canceled) {
DCHECK(overview_exit_type_.has_value());
// For kFadeOutExit EnterExitOverviewType, the home animation is scheduled in
// OnOverviewModeEnding(), so there is nothing else to do at this point.
if (canceled || *overview_exit_type_ ==
OverviewSession::EnterExitOverviewType::kFadeOutExit) {
overview_exit_type_ = base::nullopt;
return;
}
const bool animate =
*overview_exit_type_ ==
OverviewSession::EnterExitOverviewType::kSlideOutExit ||
*overview_exit_type_ ==
OverviewSession::EnterExitOverviewType::kFadeOutExit;
const HomeScreenPresenter::TransitionType transition =
*overview_exit_type_ ==
OverviewSession::EnterExitOverviewType::kFadeOutExit
? HomeScreenPresenter::TransitionType::kScaleHomeIn
: HomeScreenPresenter::TransitionType::kSlideHomeIn;
overview_exit_type_ = base::nullopt;
home_screen_presenter_.ScheduleOverviewModeAnimation(transition, animate);
// Make sure the window visibility is updated, in case it was previously
// hidden due to overview being shown.
UpdateVisibility();
}
void HomeScreenController::OnWallpaperPreviewStarted() {
in_wallpaper_preview_ = true;
UpdateVisibility();
}
void HomeScreenController::OnWallpaperPreviewEnded() {
in_wallpaper_preview_ = false;
UpdateVisibility();
}
void HomeScreenController::UpdateVisibility() {
if (!Shell::Get()->tablet_mode_controller()->InTabletMode())
return;
aura::Window* window = delegate_->GetHomeScreenWindow();
if (!window)
return;
if (ShouldShowHomeScreen())
window->Show();
else
window->Hide();
}
bool HomeScreenController::ShouldShowHomeScreen() const {
const bool in_tablet_mode =
Shell::Get()->tablet_mode_controller()->InTabletMode();
const bool in_overview =
Shell::Get()->overview_controller()->InOverviewSession();
const bool in_split_view =
SplitViewController::Get(delegate_->GetHomeScreenWindow())
->InSplitViewMode();
return in_tablet_mode && !in_overview && !in_wallpaper_preview_ &&
!in_window_dragging_ && !in_split_view;
}
} // namespace ash