chromium/ash/wm/window_animations.cc

// Copyright 2012 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "ash/wm/window_animations.h"

#include <math.h>

#include <algorithm>
#include <optional>
#include <utility>
#include <vector>

#include "ash/public/cpp/metrics_util.h"
#include "ash/public/cpp/window_animation_types.h"
#include "ash/shelf/shelf.h"
#include "ash/shell.h"
#include "ash/wm/pip/pip_positioner.h"
#include "ash/wm/resize_shadow_controller.h"
#include "ash/wm/window_util.h"
#include "ash/wm/workspace_controller.h"
#include "base/check.h"
#include "base/debug/crash_logging.h"
#include "base/debug/dump_without_crashing.h"
#include "base/functional/bind.h"
#include "base/i18n/rtl.h"
#include "base/memory/raw_ptr.h"
#include "base/metrics/histogram_functions.h"
#include "base/metrics/histogram_macros.h"
#include "base/notreached.h"
#include "base/scoped_observation.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "base/time/time.h"
#include "ui/aura/client/aura_constants.h"
#include "ui/aura/window.h"
#include "ui/aura/window_observer.h"
#include "ui/base/class_property.h"
#include "ui/compositor/animation_throughput_reporter.h"
#include "ui/compositor/compositor.h"
#include "ui/compositor/compositor_observer.h"
#include "ui/compositor/layer.h"
#include "ui/compositor/layer_animation_observer.h"
#include "ui/compositor/layer_animation_sequence.h"
#include "ui/compositor/layer_animator.h"
#include "ui/compositor/layer_observer.h"
#include "ui/compositor/layer_tree_owner.h"
#include "ui/compositor/scoped_layer_animation_settings.h"
#include "ui/display/display.h"
#include "ui/display/screen.h"
#include "ui/gfx/geometry/transform.h"
#include "ui/gfx/geometry/transform_util.h"
#include "ui/gfx/interpolated_transform.h"
#include "ui/wm/core/coordinate_conversion.h"
#include "ui/wm/core/shadow_types.h"
#include "ui/wm/core/window_animations.h"
#include "ui/wm/core/window_util.h"

namespace ash {
namespace {

const int kLayerAnimationsForMinimizeDurationMS = 200;

// Amount of time for the cross fade animation.
constexpr base::TimeDelta kCrossFadeDuration = base::Milliseconds(200);

constexpr base::TimeDelta kCrossFadeMaxDuration = base::Milliseconds(400);

// The default duration for an animation to float or unfloat a window.
static constexpr base::TimeDelta kFloatUnfloatDuration =
    base::Milliseconds(400);

// Durations for the brightness/grayscale fade animation, in milliseconds.
const int kBrightnessGrayscaleFadeDurationMs = 1000;

// Duration for fade in animation, in milliseconds.
const int kFadeInAnimationMs = 200;

// Brightness/grayscale values for hide/show window animations.
const float kWindowAnimation_HideBrightnessGrayscale = 1.f;
const float kWindowAnimation_ShowBrightnessGrayscale = 0.f;

const float kWindowAnimation_HideOpacity = 0.f;
const float kWindowAnimation_ShowOpacity = 1.f;

// Duration for gfx::Tween::ZERO animation of showing window.
constexpr base::TimeDelta kZeroAnimationMs = base::Milliseconds(300);

constexpr char kCrossFadeSmoothness[] =
    "Ash.Window.AnimationSmoothness.CrossFade";

base::TimeDelta GetCrossFadeDuration(aura::Window* window,
                                     const gfx::RectF& old_bounds,
                                     const gfx::Rect& new_bounds) {
  if (::wm::WindowAnimationsDisabled(window))
    return base::TimeDelta();

  int old_area = static_cast<int>(old_bounds.width() * old_bounds.height());
  int new_area = new_bounds.width() * new_bounds.height();
  int max_area = std::max(old_area, new_area);
  // Avoid divide by zero.
  if (max_area == 0)
    return kCrossFadeDuration;

  int delta_area = std::abs(old_area - new_area);
  // If the area didn't change, the animation is instantaneous.
  if (delta_area == 0)
    return kCrossFadeDuration;

  float factor = static_cast<float>(delta_area) / static_cast<float>(max_area);
  const auto kRange = kCrossFadeMaxDuration - kCrossFadeDuration;
  return kCrossFadeDuration + factor * kRange;
}

// Defines an observer that can be used to monitor the destruction of a given
// layer and dump without crashing when that happens. This is needed to
// investigate a crash that happens within `CrossFadeAnimationInternal()` due to
// using a layer after it has been destroyed.
// TODO(http://b/333095196): Remove this once the root cause of the crash is
// found and fixed.
class LayerDeletionDumper : public ui::LayerObserver {
 public:
  explicit LayerDeletionDumper(ui::Layer* layer) {
    observation_.Observe(layer);
  }
  LayerDeletionDumper(const LayerDeletionDumper&) = delete;
  LayerDeletionDumper& operator=(const LayerDeletionDumper&) = delete;
  ~LayerDeletionDumper() override = default;

  // ui::LayerObserver:
  void LayerDestroyed(ui::Layer* layer) override {
    observation_.Reset();
    base::debug::DumpWithoutCrashing();
  }

 private:
  base::ScopedObservation<ui::Layer, ui::LayerObserver> observation_{this};
};

// Observer for a window cross-fade animation. If either the window closes or
// the layer's animation completes, it deletes the layer and removes itself as
// an observer.
class CrossFadeObserver : public aura::WindowObserver,
                          public ui::ImplicitAnimationObserver {
 public:
  // Observes |window| for destruction, but does not take ownership.
  // Takes ownership of |layer_owner| and its child layers.
  CrossFadeObserver(aura::Window* window,
                    std::unique_ptr<ui::LayerTreeOwner> layer_owner,
                    std::optional<std::string> histogram_name)
      : window_(window),
        layer_(window->layer()),
        layer_owner_(std::move(layer_owner)) {
    window_->AddObserver(this);

    smoothness_tracker_ =
        layer_->GetCompositor()->RequestNewThroughputTracker();
    smoothness_tracker_->Start(
        metrics_util::ForSmoothnessV3(base::BindRepeating(
            [](const std::optional<std::string>& histogram_name,
               int smoothness) {
              if (histogram_name) {
                DCHECK(!histogram_name->empty());
                base::UmaHistogramPercentage(*histogram_name, smoothness);
              } else {
                UMA_HISTOGRAM_PERCENTAGE(kCrossFadeSmoothness, smoothness);
              }
            },
            std::move(histogram_name))));
  }
  CrossFadeObserver(const CrossFadeObserver&) = delete;
  CrossFadeObserver& operator=(const CrossFadeObserver&) = delete;
  ~CrossFadeObserver() override {
    smoothness_tracker_->Stop();
    smoothness_tracker_.reset();

    // Stop the old animator to trigger aborts or ends on any observers it may
    // have.
    layer_owner_->root()->GetAnimator()->StopAnimating();

    window_->RemoveObserver(this);
    window_ = nullptr;
    layer_ = nullptr;
  }

  // aura::WindowObserver:
  void OnWindowDestroying(aura::Window* window) override { StopAnimating(); }
  void OnWindowRemovingFromRootWindow(aura::Window* window,
                                      aura::Window* new_root) override {
    StopAnimating();
  }
  void OnWindowLayerRecreated(aura::Window* window) override {
    // If the window layer is recreated. |layer_| will hold the LayerAnimator we
    // were originally observing. Layer recreation is usually done when doing
    // another window animation, so stop this current one and trigger
    // OnImplicitAnimationsCompleted to delete ourselves.
    layer_->GetAnimator()->StopAnimating();
  }

  // ui::ImplicitAnimationObserver:
  void OnImplicitAnimationsCompleted() override {
    if (auto* resize_shadow_controller =
            Shell::Get()->resize_shadow_controller()) {
      resize_shadow_controller->OnCrossFadeAnimationCompleted(window_);
    }
    delete this;
  }

 protected:
  void StopAnimating() {
    // Trigger OnImplicitAnimationsCompleted() to be called and deletes us. If
    // no animation is running then do the deletion ourselves.
    DCHECK(window_);
    window_->layer()->GetAnimator()->StopAnimating();
  }

  // The window and the associated layer this observer is watching. The window
  // layer may be recreated during the course of the animation so |layer_| will
  // be different |window_->layer()| after construction.
  raw_ptr<aura::Window> window_;
  raw_ptr<ui::Layer> layer_;

  std::unique_ptr<ui::LayerTreeOwner> layer_owner_;

  std::optional<ui::ThroughputTracker> smoothness_tracker_;
};

// A version of CrossFadeObserver which updates its transform to match the
// visible bounds of the window it is cross-fading. It is expected users of this
// will not perform their own transform animation on the passed LayerTreeOwner.
class CrossFadeUpdateTransformObserver
    : public CrossFadeObserver,
      public ui::CompositorAnimationObserver {
 public:
  CrossFadeUpdateTransformObserver(
      aura::Window* window,
      std::unique_ptr<ui::LayerTreeOwner> layer_owner,
      std::optional<std::string> histogram_name)
      : CrossFadeObserver(window, std::move(layer_owner), histogram_name) {
    compositor_ = window->layer()->GetCompositor();
    compositor_->AddAnimationObserver(this);
  }
  CrossFadeUpdateTransformObserver(const CrossFadeUpdateTransformObserver&) =
      delete;
  CrossFadeUpdateTransformObserver& operator=(
      const CrossFadeUpdateTransformObserver&) = delete;
  ~CrossFadeUpdateTransformObserver() override {
    compositor_->RemoveAnimationObserver(this);
  }

  // CrossFadeObserver:
  void OnLayerAnimationStarted(ui::LayerAnimationSequence* sequence) override {
    DCHECK(!layer_owner_->root()->GetAnimator()->IsAnimatingProperty(
        ui::LayerAnimationElement::TRANSFORM));
    CrossFadeObserver::OnLayerAnimationStarted(sequence);
  }

  // ui::CompositorAnimationObserver:
  void OnAnimationStep(base::TimeTicks timestamp) override {
    // If these get shut down or destroyed we should delete ourselves.
    DCHECK(compositor_);
    DCHECK(window_);

    // Calculate the transform needed to place |layer_owner_| in the same bounds
    // plus transform as |window_|.
    gfx::RectF old_bounds(layer_owner_->root()->bounds());
    gfx::RectF new_bounds(window_->bounds());
    gfx::Transform new_transform = window_->transform();
    DCHECK(new_transform.IsScaleOrTranslation());

    // Apply the transform on the bounds to get the location of |window_|.
    // Transforms are calculated in a way where scale does not affect position,
    // so use the same logic here.
    gfx::RectF effective_bounds =
        new_transform.MapRect(gfx::RectF(new_bounds.size()));
    effective_bounds.Offset(new_bounds.OffsetFromOrigin());

    const gfx::Transform old_transform =
        gfx::TransformBetweenRects(old_bounds, effective_bounds);
    layer_owner_->root()->SetTransform(old_transform);
  }

  void OnCompositingShuttingDown(ui::Compositor* compositor) override {
    DCHECK_EQ(compositor_, compositor);
    StopAnimating();
  }

 private:
  raw_ptr<ui::Compositor> compositor_ = nullptr;
};

// Internal implementation of a cross fade animation. If
// |animate_old_layer_transform| is true, both new and old layers will animate
// their opacities and transforms. Otherwise, the old layer will on animate its
// opacity; its transforms will be updated via an observer.
void CrossFadeAnimationInternal(
    aura::Window* window,
    std::unique_ptr<ui::LayerTreeOwner> old_layer_owner,
    bool animate_old_layer_transform,
    std::optional<base::TimeDelta> duration,
    std::optional<gfx::Tween::Type> tween_type,
    std::optional<std::string> histogram_name) {
  ui::Layer* old_layer = old_layer_owner->root();

  // The window's layer can get recreated within the stack of this function due
  // to various reasons. We should never cache the layer as local, and always
  // get the layer from the window directly. See http://b/333095196 and
  // http://b/40059305. For example in http://b/40059305, if Overview exit
  //  animation is in the sequence, stopping animation may trigger
  // `OverviewController::OnEndingAnimationComplete` which may finally start the
  // frame animation. 'FrameHeader::FrameAnimatorView::StartAnimation' recreates
  // the window's layer.
  auto new_layer = [window]() -> ui::Layer* { return window->layer(); };

  DCHECK(old_layer);
  const gfx::Rect old_bounds(old_layer->bounds());

  gfx::Transform old_transform(old_layer->transform());
  gfx::Transform old_transform_in_root;
  old_transform_in_root.Translate(old_bounds.x(), old_bounds.y());
  old_transform_in_root.PreConcat(old_transform);
  old_transform_in_root.Translate(-old_bounds.x(), -old_bounds.y());
  gfx::RectF old_transformed_bounds =
      old_transform_in_root.MapRect(gfx::RectF(old_bounds));
  const gfx::Rect new_bounds(window->bounds());
  const bool old_on_top = (old_bounds.width() > new_bounds.width());

  SCOPED_CRASH_KEY_BOOL("333095196", "animate_old_layer_transform",
                        animate_old_layer_transform);
  SCOPED_CRASH_KEY_BOOL("333095196", "old_on_top", old_on_top);
  SCOPED_CRASH_KEY_STRING256("333095196", "window_title",
                             base::UTF16ToUTF8(window->GetTitle()));
  SCOPED_CRASH_KEY_STRING256("333095196", "new_layer_begin",
                             base::StringPrintf("%p", new_layer()));

  // Ensure the higher-resolution layer is on top.
  if (old_on_top)
    old_layer->parent()->StackBelow(new_layer(), old_layer);
  else
    old_layer->parent()->StackAbove(new_layer(), old_layer);

  // Shorten the animation if there's not much visual movement.
  const base::TimeDelta animation_duration = duration.value_or(
      GetCrossFadeDuration(window, old_transformed_bounds, new_bounds));
  const gfx::Tween::Type animation_tween_type =
      tween_type.value_or(gfx::Tween::EASE_OUT);

  // Scale up the old layer while translating to new position.
  {
    old_layer->GetAnimator()->StopAnimating();
    old_layer->SetTransform(old_transform);
    ui::ScopedLayerAnimationSettings settings(old_layer->GetAnimator());
    settings.SetTransitionDuration(animation_duration);
    settings.SetTweenType(animation_tween_type);
    settings.DeferPaint();

    if (old_on_top) {
      // Only caching render surface when there is an opacity animation and
      // multiple layers.
      if (!old_layer->children().empty())
        settings.CacheRenderSurface();
      // The old layer is on top, and should fade out. The new layer below will
      // stay opaque to block the desktop.
      old_layer->SetOpacity(kWindowAnimation_HideOpacity);
    } else if (!animate_old_layer_transform) {
      // If |animate_old_layer_transform| and |old_on_top| are both false, then
      // the old layer will have no animations and the observer will delete
      // itself (and the old layer) right away. To make sure the old layer stays
      // behind the new layer with opacity 1.f for the duration of the
      // animation, change the tween to zero.
      settings.SetTweenType(gfx::Tween::ZERO);
      old_layer->SetOpacity(kWindowAnimation_HideOpacity);
    }

    if (animate_old_layer_transform) {
      gfx::Transform out_transform;
      float scale_x =
          new_bounds.width() / static_cast<float>(old_bounds.width());
      float scale_y =
          new_bounds.height() / static_cast<float>(old_bounds.height());
      out_transform.Translate(new_bounds.x() - old_bounds.x(),
                              new_bounds.y() - old_bounds.y());
      out_transform.Scale(scale_x, scale_y);
      old_layer->SetTransform(out_transform);
    }
    // In tests |old_layer| is deleted here, as animations have zero duration.
    old_layer = nullptr;
  }

  SCOPED_CRASH_KEY_STRING256("333095196", "new_layer_mid",
                             base::StringPrintf("%p", new_layer()));

  // Create an observer that would dump without crashing if `new_layer()` gets
  // destroyed within the remaining scope of this function. This is needed to
  // investigate the root cause of http://b/333095196.
  // TODO(http://b/333095196): Remove this code and all crash keys once the
  // issue is resolved.
  LayerDeletionDumper deletion_dumper(new_layer());

  // Set the new layer's current transform, such that the user sees a scaled
  // version of the window with the original bounds at the original position.
  gfx::Transform in_transform;
  const float scale_x =
      old_transformed_bounds.width() / static_cast<float>(new_bounds.width());
  const float scale_y =
      old_transformed_bounds.height() / static_cast<float>(new_bounds.height());
  in_transform.Translate(old_transformed_bounds.x() - new_bounds.x(),
                         old_transformed_bounds.y() - new_bounds.y());
  in_transform.Scale(scale_x, scale_y);
  new_layer()->SetTransform(in_transform);
  if (!old_on_top) {
    // The new layer is on top and should fade in.  The old layer below will
    // stay opaque and block the desktop.
    new_layer()->SetOpacity(kWindowAnimation_HideOpacity);
  }
  {
    // Animation observer owns the old layer and deletes itself. It should be
    // attached to the new layer so that if the new layer animation gets
    // aborted, we can delete the old layer.
    CrossFadeObserver* observer =
        animate_old_layer_transform
            ? new CrossFadeObserver(window, std::move(old_layer_owner),
                                    histogram_name)
            : new CrossFadeUpdateTransformObserver(
                  window, std::move(old_layer_owner), histogram_name);

    // Animate the new layer to the identity transform, so the window goes to
    // its newly set bounds.
    ui::ScopedLayerAnimationSettings settings(new_layer()->GetAnimator());
    settings.AddObserver(observer);
    settings.SetTransitionDuration(animation_duration);
    settings.SetTweenType(animation_tween_type);
    settings.DeferPaint();
    if (!old_on_top) {
      // Only caching render surface when there is an opacity animation and
      // multiple layers.
      if (!new_layer()->children().empty()) {
        settings.CacheRenderSurface();
      }
      // New layer is on top, fade it in.
      new_layer()->SetOpacity(kWindowAnimation_ShowOpacity);
    }
    SCOPED_CRASH_KEY_NUMBER("333095196", "animation_duration_ms",
                            animation_duration.InMillisecondsF());
    SCOPED_CRASH_KEY_BOOL("333095196", "is_destroying",
                          window->is_destroying());
    SCOPED_CRASH_KEY_STRING256("333095196", "new_layer_end",
                               base::StringPrintf("%p", new_layer()));
    new_layer()->SetTransform(gfx::Transform());
  }
}

}  // namespace

void SetTransformForScaleAnimation(ui::Layer* layer,
                                   LayerScaleAnimationDirection type) {
  // Scales for windows above and below the current workspace.
  constexpr float kLayerScaleAboveSize = 1.1f;
  constexpr float kLayerScaleBelowSize = .9f;

  const float scale = type == LAYER_SCALE_ANIMATION_ABOVE
                          ? kLayerScaleAboveSize
                          : kLayerScaleBelowSize;
  gfx::Transform transform;
  transform.Translate(-layer->bounds().width() * (scale - 1.0f) / 2,
                      -layer->bounds().height() * (scale - 1.0f) / 2);
  transform.Scale(scale, scale);
  layer->SetTransform(transform);
}

void AddLayerAnimationsForMinimize(aura::Window* window, bool show) {
  // Recalculate the transform at restore time since the launcher item may have
  // moved while the window was minimized.
  gfx::Rect bounds = window->bounds();
  gfx::Rect target_bounds = GetMinimizeAnimationTargetBoundsInScreen(window);
  ::wm::ConvertRectFromScreen(window->parent(), &target_bounds);

  float scale_x = static_cast<float>(target_bounds.width()) / bounds.width();
  float scale_y = static_cast<float>(target_bounds.height()) / bounds.height();

  std::unique_ptr<ui::InterpolatedTransform> scale =
      std::make_unique<ui::InterpolatedScale>(
          gfx::Point3F(1, 1, 1), gfx::Point3F(scale_x, scale_y, 1));

  std::unique_ptr<ui::InterpolatedTransform> translation =
      std::make_unique<ui::InterpolatedTranslation>(
          gfx::PointF(), gfx::PointF(target_bounds.x() - bounds.x(),
                                     target_bounds.y() - bounds.y()));

  scale->SetChild(std::move(translation));
  scale->SetReversed(show);

  base::TimeDelta duration =
      window->layer()->GetAnimator()->GetTransitionDuration();

  std::unique_ptr<ui::LayerAnimationElement> transition =
      ui::LayerAnimationElement::CreateInterpolatedTransformElement(
          std::move(scale), duration);

  transition->set_tween_type(show ? gfx::Tween::EASE_IN
                                  : gfx::Tween::EASE_IN_OUT);

  window->layer()->GetAnimator()->ScheduleAnimation(
      new ui::LayerAnimationSequence(std::move(transition)));

  // When hiding a window, turn off blending until the animation is 3 / 4 done
  // to save bandwidth and reduce jank.
  if (!show) {
    window->layer()->GetAnimator()->SchedulePauseForProperties(
        (duration * 3) / 4, ui::LayerAnimationElement::OPACITY);
  }

  // Fade in and out quickly when the window is small to reduce jank.
  float opacity = show ? 1.0f : 0.0f;
  window->layer()->GetAnimator()->ScheduleAnimation(
      new ui::LayerAnimationSequence(
          ui::LayerAnimationElement::CreateOpacityElement(opacity,
                                                          duration / 4)));

  // Reset the transform to identity when the minimize animation is completed.
  window->layer()->GetAnimator()->ScheduleAnimation(
      new ui::LayerAnimationSequence(
          ui::LayerAnimationElement::CreateTransformElement(
              gfx::Transform(), base::TimeDelta())));
}

void AnimateShowWindow_Minimize(aura::Window* window) {
  window->layer()->SetOpacity(kWindowAnimation_HideOpacity);
  ui::ScopedLayerAnimationSettings settings(window->layer()->GetAnimator());
  ui::AnimationThroughputReporter reporter(
      settings.GetAnimator(),
      metrics_util::ForSmoothnessV3(
          base::BindRepeating(static_cast<void (*)(const char*, int)>(
                                  &base::UmaHistogramPercentage),
                              "Ash.Window.AnimationSmoothness.Unminimize")));
  base::TimeDelta duration =
      base::Milliseconds(kLayerAnimationsForMinimizeDurationMS);
  settings.SetTransitionDuration(duration);
  AddLayerAnimationsForMinimize(window, true);

  // Now that the window has been restored, we need to clear its animation style
  // to default so that normal animation applies.
  ::wm::SetWindowVisibilityAnimationType(
      window, ::wm::WINDOW_VISIBILITY_ANIMATION_TYPE_DEFAULT);
}

void AnimateHideWindow_Minimize(aura::Window* window) {
  // Property sets within this scope will be implicitly animated.
  ::wm::ScopedHidingAnimationSettings hiding_settings(window);
  // Report animation smoothness for animations created within this scope.
  ui::AnimationThroughputReporter reporter(
      hiding_settings.layer_animation_settings()->GetAnimator(),
      metrics_util::ForSmoothnessV3(
          base::BindRepeating(static_cast<void (*)(const char*, int)>(
                                  &base::UmaHistogramPercentage),
                              "Ash.Window.AnimationSmoothness.Minimize")));

  base::TimeDelta duration =
      base::Milliseconds(kLayerAnimationsForMinimizeDurationMS);

  hiding_settings.layer_animation_settings()->SetTransitionDuration(duration);
  window->layer()->SetVisible(false);

  AddLayerAnimationsForMinimize(window, false);
}

void AnimateShowHideWindowCommon_BrightnessGrayscale(aura::Window* window,
                                                     bool show) {
  float start_value, end_value;
  if (show) {
    start_value = kWindowAnimation_HideBrightnessGrayscale;
    end_value = kWindowAnimation_ShowBrightnessGrayscale;
  } else {
    start_value = kWindowAnimation_ShowBrightnessGrayscale;
    end_value = kWindowAnimation_HideBrightnessGrayscale;
  }

  window->layer()->SetLayerBrightness(start_value);
  window->layer()->SetLayerGrayscale(start_value);
  if (show) {
    window->layer()->SetOpacity(kWindowAnimation_ShowOpacity);
    window->layer()->SetVisible(true);
  }

  base::TimeDelta duration =
      base::Milliseconds(kBrightnessGrayscaleFadeDurationMs);

  if (show) {
    ui::ScopedLayerAnimationSettings settings(window->layer()->GetAnimator());
    window->layer()->GetAnimator()->ScheduleTogether(
        CreateBrightnessGrayscaleAnimationSequence(end_value, duration));
  } else {
    ::wm::ScopedHidingAnimationSettings hiding_settings(window);
    window->layer()->GetAnimator()->ScheduleTogether(
        CreateBrightnessGrayscaleAnimationSequence(end_value, duration));
    window->layer()->SetOpacity(kWindowAnimation_HideOpacity);
    window->layer()->SetVisible(false);
  }
}

void AnimateShowWindow_BrightnessGrayscale(aura::Window* window) {
  AnimateShowHideWindowCommon_BrightnessGrayscale(window, true);
}

// TODO(edcourtney): Consolidate with AnimateShowWindow_Fade in ui/wm/core.
void AnimateShowWindow_FadeIn(aura::Window* window) {
  window->layer()->SetOpacity(kWindowAnimation_HideOpacity);
  ui::ScopedLayerAnimationSettings settings(window->layer()->GetAnimator());
  settings.SetTransitionDuration(base::Milliseconds(kFadeInAnimationMs));
  window->layer()->SetVisible(true);
  window->layer()->SetOpacity(kWindowAnimation_ShowOpacity);
}

void AnimateHideWindow_BrightnessGrayscale(aura::Window* window) {
  AnimateShowHideWindowCommon_BrightnessGrayscale(window, false);
}

void AnimateHideWindow_SlideOut(aura::Window* window) {
  base::TimeDelta duration =
      base::Milliseconds(PipPositioner::kPipDismissTimeMs);

  ::wm::ScopedHidingAnimationSettings settings(window);
  settings.layer_animation_settings()->SetTransitionDuration(duration);
  window->layer()->SetOpacity(kWindowAnimation_HideOpacity);
  window->layer()->SetVisible(false);

  gfx::Rect bounds = window->GetBoundsInScreen();
  display::Display display =
      display::Screen::GetScreen()->GetDisplayNearestWindow(window);
  gfx::Rect dismissed_bounds =
      PipPositioner::GetDismissedPosition(display, bounds);
  ::wm::ConvertRectFromScreen(window->parent(), &dismissed_bounds);
  window->layer()->SetBounds(dismissed_bounds);
}

void AnimateShowWindow_StepEnd(aura::Window* window) {
  window->layer()->SetOpacity(kWindowAnimation_HideOpacity);
  ui::ScopedLayerAnimationSettings settings(window->layer()->GetAnimator());
  settings.SetTransitionDuration(kZeroAnimationMs);
  settings.SetTweenType(gfx::Tween::ZERO);
  window->layer()->SetOpacity(kWindowAnimation_ShowOpacity);
}

void AnimateHideWindow_StepEnd(aura::Window* window) {
  ::wm::ScopedHidingAnimationSettings settings(window);
  settings.layer_animation_settings()->SetTransitionDuration(kZeroAnimationMs);
  settings.layer_animation_settings()->SetTweenType(gfx::Tween::ZERO);
  window->layer()->SetVisible(false);
}

bool AnimateShowWindow(aura::Window* window) {
  if (!::wm::HasWindowVisibilityAnimationTransition(window,
                                                    ::wm::ANIMATE_SHOW)) {
    return false;
  }

  switch (::wm::GetWindowVisibilityAnimationType(window)) {
    case WINDOW_VISIBILITY_ANIMATION_TYPE_MINIMIZE:
      AnimateShowWindow_Minimize(window);
      return true;
    case WINDOW_VISIBILITY_ANIMATION_TYPE_BRIGHTNESS_GRAYSCALE:
      AnimateShowWindow_BrightnessGrayscale(window);
      return true;
    case WINDOW_VISIBILITY_ANIMATION_TYPE_FADE_IN_SLIDE_OUT:
      AnimateShowWindow_FadeIn(window);
      return true;
    case WINDOW_VISIBILITY_ANIMATION_TYPE_STEP_END:
      AnimateShowWindow_StepEnd(window);
      return true;
    default:
      NOTREACHED();
  }
}

bool AnimateHideWindow(aura::Window* window) {
  if (!::wm::HasWindowVisibilityAnimationTransition(window,
                                                    ::wm::ANIMATE_HIDE)) {
    return false;
  }

  switch (::wm::GetWindowVisibilityAnimationType(window)) {
    case WINDOW_VISIBILITY_ANIMATION_TYPE_MINIMIZE:
      AnimateHideWindow_Minimize(window);
      return true;
    case WINDOW_VISIBILITY_ANIMATION_TYPE_BRIGHTNESS_GRAYSCALE:
      AnimateHideWindow_BrightnessGrayscale(window);
      return true;
    case WINDOW_VISIBILITY_ANIMATION_TYPE_FADE_IN_SLIDE_OUT:
      AnimateHideWindow_SlideOut(window);
      return true;
    case WINDOW_VISIBILITY_ANIMATION_TYPE_STEP_END:
      AnimateHideWindow_StepEnd(window);
      return true;
    default:
      NOTREACHED();
  }
}

void CrossFadeAnimation(aura::Window* window,
                        std::unique_ptr<ui::LayerTreeOwner> old_layer_owner) {
  CrossFadeAnimationInternal(
      window, std::move(old_layer_owner), /*animate_old_layer_transform=*/true,
      /*duration=*/std::nullopt, /*tween_type=*/std::nullopt,
      /*histogram_name=*/std::nullopt);
}

void CrossFadeAnimationForFloatUnfloat(
    aura::Window* window,
    std::unique_ptr<ui::LayerTreeOwner> old_layer_owner,
    bool to_float) {
  CrossFadeAnimationInternal(window, std::move(old_layer_owner),
                             /*animate_old_layer_transform=*/true,
                             kFloatUnfloatDuration,
                             to_float ? gfx::Tween::Type::ACCEL_30_DECEL_20_85
                                      : gfx::Tween::Type::FAST_OUT_SLOW_IN_3,
                             /*histogram_name=*/std::nullopt);
}

void CrossFadeAnimationAnimateNewLayerOnly(aura::Window* window,
                                           const gfx::Rect& target_bounds,
                                           base::TimeDelta duration,
                                           gfx::Tween::Type tween_type,
                                           const std::string& histogram_name) {
  std::unique_ptr<ui::LayerTreeOwner> old_layer_owner =
      ::wm::RecreateLayers(window);
  window->SetBounds(target_bounds);
  CrossFadeAnimationInternal(window, std::move(old_layer_owner),
                             /*animate_old_layer=*/false, duration, tween_type,
                             histogram_name);
}

bool AnimateOnChildWindowVisibilityChanged(aura::Window* window, bool visible) {
  if (::wm::WindowAnimationsDisabled(window))
    return false;
  // Attempt to run CoreWm supplied animation types.
  if (::wm::AnimateOnChildWindowVisibilityChanged(window, visible))
    return true;
  // Otherwise try to run an Ash-specific animation.
  if (visible)
    return AnimateShowWindow(window);
  // Don't start hiding the window again if it's already being hidden.
  return window->layer()->GetTargetOpacity() != 0.0f &&
         AnimateHideWindow(window);
}

std::vector<ui::LayerAnimationSequence*>
CreateBrightnessGrayscaleAnimationSequence(float target_value,
                                           base::TimeDelta duration) {
  gfx::Tween::Type animation_type = gfx::Tween::EASE_OUT;
  std::unique_ptr<ui::LayerAnimationSequence> brightness_sequence =
      std::make_unique<ui::LayerAnimationSequence>();
  std::unique_ptr<ui::LayerAnimationSequence> grayscale_sequence =
      std::make_unique<ui::LayerAnimationSequence>();

  std::unique_ptr<ui::LayerAnimationElement> brightness_element =
      ui::LayerAnimationElement::CreateBrightnessElement(target_value,
                                                         duration);
  brightness_element->set_tween_type(animation_type);
  brightness_sequence->AddElement(std::move(brightness_element));

  std::unique_ptr<ui::LayerAnimationElement> grayscale_element =
      ui::LayerAnimationElement::CreateGrayscaleElement(target_value, duration);
  grayscale_element->set_tween_type(animation_type);
  grayscale_sequence->AddElement(std::move(grayscale_element));

  std::vector<ui::LayerAnimationSequence*> animations;
  animations.push_back(brightness_sequence.release());
  animations.push_back(grayscale_sequence.release());

  return animations;
}

gfx::Rect GetMinimizeAnimationTargetBoundsInScreen(aura::Window* window) {
  Shelf* shelf = Shelf::ForWindow(window);
  gfx::Rect item_rect = shelf->GetScreenBoundsOfItemIconForWindow(window);

  // The launcher item is visible and has an icon.
  if (!item_rect.IsEmpty())
    return item_rect;

  // If both the icon width and height are 0, then there is no icon in the
  // launcher for |window|. If the launcher is auto hidden, one of the height or
  // width will be 0 but the position in the launcher and the major dimension
  // are still reported correctly and the window can be animated to the launcher
  // item's light bar.
  if (item_rect.width() != 0 || item_rect.height() != 0) {
    if (shelf->GetVisibilityState() == SHELF_AUTO_HIDE) {
      gfx::Rect shelf_bounds = shelf->GetWindow()->GetBoundsInScreen();
      if (shelf->alignment() == ShelfAlignment::kLeft)
        item_rect.set_x(shelf_bounds.right());
      else if (shelf->alignment() == ShelfAlignment::kRight)
        item_rect.set_x(shelf_bounds.x());
      else
        item_rect.set_y(shelf_bounds.y());
      return item_rect;
    }
  }

  // Coming here, there is no visible icon of that shelf item and we zoom back
  // to the location of the application launcher (which is fixed as first item
  // of the shelf).
  gfx::Rect work_area =
      display::Screen::GetScreen()->GetDisplayNearestWindow(window).work_area();
  int ltr_adjusted_x = base::i18n::IsRTL() ? work_area.right() : work_area.x();
  switch (shelf->alignment()) {
    case ShelfAlignment::kBottom:
    case ShelfAlignment::kBottomLocked:
      return gfx::Rect(ltr_adjusted_x, work_area.bottom(), 0, 0);
    case ShelfAlignment::kLeft:
      return gfx::Rect(work_area.x(), work_area.y(), 0, 0);
    case ShelfAlignment::kRight:
      return gfx::Rect(work_area.right(), work_area.y(), 0, 0);
  }
  NOTREACHED();
}

void BounceWindow(aura::Window* window) {
  wm::AnimateWindow(window, wm::WINDOW_ANIMATION_TYPE_BOUNCE);
}

}  // namespace ash