chromium/ash/shelf/hotseat_transition_animator.cc

// Copyright 2019 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/shelf/hotseat_transition_animator.h"

#include "ash/public/cpp/metrics_util.h"
#include "ash/public/cpp/shelf_config.h"
#include "ash/shelf/drag_handle.h"
#include "ash/shelf/shelf_layout_manager.h"
#include "ash/shelf/shelf_widget.h"
#include "ash/shell.h"
#include "base/check.h"
#include "base/functional/bind.h"
#include "base/metrics/histogram_macros.h"
#include "ui/compositor/animation_throughput_reporter.h"
#include "ui/compositor/layer.h"
#include "ui/compositor/layer_animator.h"
#include "ui/compositor/scoped_layer_animation_settings.h"
#include "ui/display/screen.h"
#include "ui/gfx/animation/tween.h"
#include "ui/gfx/geometry/rect.h"

namespace ash {

namespace {

void ReportSmoothness(HotseatState new_state, int value) {
  switch (new_state) {
    case HotseatState::kShownClamshell:
    case HotseatState::kShownHomeLauncher:
      UMA_HISTOGRAM_PERCENTAGE(
          "Ash.HotseatTransition.AnimationSmoothness."
          "TransitionToShownHotseat",
          value);
      break;
    case HotseatState::kExtended:
      UMA_HISTOGRAM_PERCENTAGE(
          "Ash.HotseatTransition.AnimationSmoothness."
          "TransitionToExtendedHotseat",
          value);
      break;
    case HotseatState::kHidden:
      UMA_HISTOGRAM_PERCENTAGE(
          "Ash.HotseatTransition.AnimationSmoothness."
          "TransitionToHiddenHotseat",
          value);
      break;
    case HotseatState::kNone:
      DCHECK(false);
      break;
  }
}

}  // namespace

HotseatTransitionAnimator::HotseatTransitionAnimator(ShelfWidget* shelf_widget)
    : shelf_widget_(shelf_widget) {}

HotseatTransitionAnimator::~HotseatTransitionAnimator() {
  StopObservingImplicitAnimations();
}

void HotseatTransitionAnimator::OnHotseatStateChanged(HotseatState old_state,
                                                      HotseatState new_state) {
  DoAnimation(old_state, new_state);
}

void HotseatTransitionAnimator::AddObserver(Observer* observer) {
  observers_.AddObserver(observer);
}

void HotseatTransitionAnimator::RemoveObserver(Observer* observer) {
  observers_.RemoveObserver(observer);
}

void HotseatTransitionAnimator::OnImplicitAnimationsCompleted() {
  std::move(animation_complete_callback_).Run();

  if (test_observer_)
    test_observer_->OnTransitionTestAnimationEnded();
}

void HotseatTransitionAnimator::OnLayerAnimationAborted(
    ui::LayerAnimationSequence* sequence) {
  // NOTE: This will be called only once (or zero times) each time for`
  // DoAnimation because we only have one LayerAnimationSequence for this
  // particular animation. If another is added we will have to modify how this
  // is sent out.
  for (auto& observer : observers_)
    observer.OnHotseatTransitionAnimationAborted();
}

void HotseatTransitionAnimator::SetAnimationsEnabledInSessionState(
    bool enabled) {
  animations_enabled_for_current_session_state_ = enabled;

  ui::Layer* animating_background = shelf_widget_->GetAnimatingBackground();
  if (!enabled && animating_background->GetAnimator()->is_animating())
    animating_background->GetAnimator()->StopAnimating();
}

void HotseatTransitionAnimator::SetTestObserver(TestObserver* test_observer) {
  test_observer_ = test_observer;
}

void HotseatTransitionAnimator::DoAnimation(HotseatState old_state,
                                            HotseatState new_state) {
  const bool animating_to_shown_background =
      new_state != HotseatState::kShownHomeLauncher;
  gfx::Transform transform;
  if (animating_to_shown_background)
    transform.Translate(0, -ShelfConfig::Get()->in_app_shelf_size());

  if (!ShouldDoAnimation(old_state, new_state)) {
    shelf_widget_->GetAnimatingBackground()->SetTransform(transform);
    return;
  }

  StopObservingImplicitAnimations();

  shelf_widget_->GetAnimatingBackground()->SetColor(
      ShelfConfig::Get()->GetMaximizedShelfColor(shelf_widget_));

  gfx::Rect drag_handle_bounds(shelf_widget_->GetAnimatingBackground()->size());
  drag_handle_bounds.ClampToCenteredSize(ShelfConfig::Get()->DragHandleSize());
  shelf_widget_->GetAnimatingDragHandle()->SetBounds(drag_handle_bounds);

  for (auto& observer : observers_)
    observer.OnHotseatTransitionAnimationWillStart(old_state, new_state);

  {
    ui::ScopedLayerAnimationSettings shelf_bg_animation_setter(
        shelf_widget_->GetAnimatingBackground()->GetAnimator());
    shelf_bg_animation_setter.SetTransitionDuration(
        ShelfConfig::Get()->hotseat_background_animation_duration());
    shelf_bg_animation_setter.SetTweenType(gfx::Tween::EASE_OUT);
    shelf_bg_animation_setter.SetPreemptionStrategy(
        ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET);
    animation_complete_callback_ = base::BindOnce(
        &HotseatTransitionAnimator::NotifyHotseatTransitionAnimationEnded,
        weak_ptr_factory_.GetWeakPtr(), old_state, new_state);
    shelf_bg_animation_setter.AddObserver(this);

    ui::AnimationThroughputReporter reporter(
        shelf_bg_animation_setter.GetAnimator(),
        metrics_util::ForSmoothnessV3(
            base::BindRepeating(&ReportSmoothness, new_state)));

    shelf_widget_->GetAnimatingBackground()->SetTransform(transform);
  }
}

bool HotseatTransitionAnimator::ShouldDoAnimation(HotseatState old_state,
                                                  HotseatState new_state) {
  if (!animations_enabled_for_current_session_state_)
    return false;

  // The shelf should be directly hidden without animation if the auto hide
  // state is auto hidden.
  if (shelf_widget_->shelf_layout_manager()->is_shelf_auto_hidden())
    return false;

  return (new_state == HotseatState::kShownHomeLauncher ||
          old_state == HotseatState::kShownHomeLauncher) &&
         !(new_state == HotseatState::kShownClamshell ||
           old_state == HotseatState::kShownClamshell) &&
         display::Screen::GetScreen()->InTabletMode();
}

void HotseatTransitionAnimator::NotifyHotseatTransitionAnimationEnded(
    HotseatState old_state,
    HotseatState new_state) {
  for (auto& observer : observers_)
    observer.OnHotseatTransitionAnimationEnded(old_state, new_state);
}

}  // namespace ash