chromium/ash/wm/overview/birch/birch_bar_view.cc

// Copyright 2024 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/overview/birch/birch_bar_view.h"

#include <array>
#include <ostream>
#include <vector>

#include "ash/birch/birch_item.h"
#include "ash/public/cpp/shelf_config.h"
#include "ash/public/cpp/shelf_types.h"
#include "ash/public/cpp/window_properties.h"
#include "ash/root_window_settings.h"
#include "ash/shelf/shelf.h"
#include "ash/shell.h"
#include "ash/wm/overview/birch/birch_chip_loader_view.h"
#include "ash/wm/overview/birch/birch_privacy_nudge_controller.h"
#include "ash/wm/window_properties.h"
#include "base/containers/contains.h"
#include "base/containers/fixed_flat_set.h"
#include "base/time/time.h"
#include "third_party/abseil-cpp/absl/cleanup/cleanup.h"
#include "ui/base/metadata/metadata_header_macros.h"
#include "ui/base/metadata/metadata_impl_macros.h"
#include "ui/compositor/layer.h"
#include "ui/display/display.h"
#include "ui/display/screen.h"
#include "ui/gfx/geometry/transform_util.h"
#include "ui/views/animation/animation_builder.h"
#include "ui/views/metadata/view_factory_internal.h"
#include "ui/views/view_class_properties.h"

namespace ash {

namespace {

// The spacing between chips and chips rows.
constexpr int kChipSpacing = 8;
// Horizontal paddings of the bar container.
constexpr int kContainerHorizontalPaddingNoShelf = 32;
constexpr int kContainerHorizontalPaddingWithShelf = 64;
// The height of the chips.
constexpr int kChipHeight = 64;
// The optimal chip width for large screen.
constexpr int kOptimalChipWidth = 278;
// The threshold for large screen.
constexpr int kLargeScreenThreshold = 1450;
// The chips row capacity for different layout types.
constexpr unsigned kRowCapacityOf2x2Layout = 2;
constexpr unsigned kRowCapacityOf1x4Layout = 4;

// The delays of chip loading animations corresponding to the chip positions on
// the bar.
constexpr std::array<base::TimeDelta, 4> kLoaderAnimationDelays{
    base::Milliseconds(250), base::Milliseconds(450), base::Milliseconds(600),
    base::Milliseconds(700)};

// The delays of chip reloading animations corresponding to the chip positions
// on the bar.
constexpr std::array<base::TimeDelta, 4> kReloaderAnimationDelays{
    base::Milliseconds(0), base::Milliseconds(200), base::Milliseconds(350),
    base::Milliseconds(450)};

// The delays of fading in animations.
constexpr base::TimeDelta kFadeInDelayAfterLoading = base::Milliseconds(200);
constexpr base::TimeDelta kFadeInDelayAfterLoadingByUser =
    base::Milliseconds(100);

// The durations of chip button animations.
constexpr base::TimeDelta kFadeInDurationAfterLoading = base::Milliseconds(150);
constexpr base::TimeDelta kFadeInDurationAfterLoadingByUser =
    base::Milliseconds(200);
constexpr base::TimeDelta kFadeInDurationAfterLoadingForInformedRestore =
    base::Milliseconds(400);
constexpr base::TimeDelta kFadeInDurationAfterReloading =
    base::Milliseconds(200);
constexpr base::TimeDelta kFadeOutChipsDurationBeforeReloading =
    base::Milliseconds(200);
constexpr base::TimeDelta kFadeOutChipsDurationOnHidingByUser =
    base::Milliseconds(100);
constexpr base::TimeDelta kFadeOutChipDurationOnRemoving =
    base::Milliseconds(100);
constexpr base::TimeDelta kSlidingChipsDelayOnRemoving = base::Milliseconds(50);
constexpr base::TimeDelta kSlidingChipsDurationOnRemoving =
    base::Milliseconds(300);
constexpr base::TimeDelta kSlidingChipDelayOnAttachment =
    base::Milliseconds(50);
constexpr base::TimeDelta kSlidingChipDurationOnAttachment =
    base::Milliseconds(300);
constexpr base::TimeDelta kFadeInChipDelayOnAttachment =
    base::Milliseconds(200);
constexpr base::TimeDelta kFadeInChipDurationOnAttachment =
    base::Milliseconds(150);

// Calculates the space for each chip according to the available space and
// number of chips.
int GetChipSpace(int available_size, int chips_num) {
  return chips_num
             ? (available_size - (chips_num - 1) * kChipSpacing) / chips_num
             : available_size;
}

// Creates a chips row.
std::unique_ptr<views::BoxLayoutView> CreateChipsRow() {
  return views::Builder<views::BoxLayoutView>()
      .SetMainAxisAlignment(views::BoxLayout::MainAxisAlignment::kStart)
      .SetCrossAxisAlignment(views::BoxLayout::CrossAxisAlignment::kCenter)
      .SetBetweenChildSpacing(kChipSpacing)
      .Build();
}

using State = BirchBarView::State;

// Checks if the given state is a loading state.
bool IsLoadingState(State state) {
  return state == State::kLoading || state == State::kLoadingByUser ||
         state == State::kLoadingForInformedRestore ||
         state == State::kReloading;
}

#if DCHECK_IS_ON()
// Gets the string of given state.
std::ostream& operator<<(std::ostream& stream, State state) {
  switch (state) {
    case State::kLoading:
      return stream << "loading";
    case State::kLoadingForInformedRestore:
      return stream << "loading for informed restore";
    case State::kLoadingByUser:
      return stream << "loading by user";
    case State::kReloading:
      return stream << "reloading";
    case State::kShuttingDown:
      return stream << "shutting down";
    case State::kNormal:
      return stream << "normal";
  }
}

// Checks if the state transition is valid.
bool IsValidStateTransition(State current_state, State new_state) {
  static const auto kStateTransitions =
      base::MakeFixedFlatSet<std::pair<State, State>>({
          // From loading state to reloading state and other non-loading states.
          {State::kLoading, State::kReloading},
          {State::kLoading, State::kShuttingDown},
          {State::kLoading, State::kNormal},
          // From loading for informed restore to reloading state and other
          // non-loading states.
          {State::kLoadingForInformedRestore, State::kReloading},
          {State::kLoadingForInformedRestore, State::kShuttingDown},
          {State::kLoadingForInformedRestore, State::kNormal},
          // From loading by user state to reloading state and other non-loading
          // states.
          {State::kLoadingByUser, State::kReloading},
          {State::kLoadingByUser, State::kShuttingDown},
          {State::kLoadingByUser, State::kNormal},
          // From reloading state to other non-loading states.
          {State::kReloading, State::kShuttingDown},
          {State::kReloading, State::kNormal},
          // From normal state to all the other states.
          {State::kNormal, State::kLoading},
          {State::kNormal, State::kLoadingForInformedRestore},
          {State::kNormal, State::kLoadingByUser},
          {State::kNormal, State::kReloading},
          {State::kNormal, State::kShuttingDown},
      });

  return kStateTransitions.contains({current_state, new_state});
}
#endif

}  // namespace

BirchBarView::BirchBarView(aura::Window* root_window)
    : chip_size_(GetChipSize(root_window)) {
  // Build up a 2 levels nested box layout hierarchy.
  using MainAxisAlignment = views::BoxLayout::MainAxisAlignment;
  using CrossAxisAlignment = views::BoxLayout::CrossAxisAlignment;
  views::Builder<views::BoxLayoutView>(this)
      .SetOrientation(views::BoxLayout::Orientation::kVertical)
      .SetMainAxisAlignment(MainAxisAlignment::kCenter)
      .SetCrossAxisAlignment(CrossAxisAlignment::kStart)
      .SetBetweenChildSpacing(kChipSpacing)
      .AddChildren(views::Builder<views::BoxLayoutView>()
                       .CopyAddressTo(&primary_row_)
                       .SetMainAxisAlignment(MainAxisAlignment::kStart)
                       .SetCrossAxisAlignment(CrossAxisAlignment::kCenter)
                       .SetBetweenChildSpacing(kChipSpacing))
      .SetPaintToLayer()
      .BuildChildren();

  layer()->SetFillsBoundsOpaquely(false);
}

BirchBarView::~BirchBarView() = default;

// static
std::unique_ptr<views::Widget> BirchBarView::CreateBirchBarWidget(
    aura::Window* root_window) {
  views::Widget::InitParams params(
      views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET,
      views::Widget::InitParams::TYPE_WINDOW_FRAMELESS);
  params.accept_events = true;
  params.activatable = views::Widget::InitParams::Activatable::kYes;
  params.opacity = views::Widget::InitParams::WindowOpacity::kTranslucent;
  params.context = root_window;
  params.name = "BirchBarWidget";
  params.init_properties_container.SetProperty(kOverviewUiKey, true);
  params.init_properties_container.SetProperty(kHideInDeskMiniViewKey, true);

  auto widget = std::make_unique<views::Widget>(std::move(params));
  widget->SetContentsView(std::make_unique<BirchBarView>(root_window));
  widget->ShowInactive();
  return widget;
}

void BirchBarView::SetState(State state) {
  if (state_ == state) {
    return;
  }

#if DCHECK_IS_ON()
  if (!IsValidStateTransition(state_, state)) {
    NOTREACHED() << "Transition from state " << state_ << " to state " << state
                 << " is invalid.";
  }
#endif

  const State current_state = state_;
  state_ = state;
  switch (state_) {
    case State::kLoadingForInformedRestore:
      AddLoadingChips();
      break;
    case State::kReloading:
      if (IsLoadingState(current_state)) {
        AddReloadingChips();
      } else if (current_state == State::kNormal) {
        FadeOutChips();
      }
      break;
    case State::kShuttingDown:
      if (IsLoadingState(current_state)) {
        Clear();
      } else if (current_state == State::kNormal) {
        FadeOutChips();
      }
      break;
    case State::kLoading:
    case State::kLoadingByUser:
    case State::kNormal:
      break;
  }
}

void BirchBarView::ShutdownChips() {
  for (BirchChipButtonBase* chip : chips_) {
    chip->Shutdown();
  }
}

void BirchBarView::UpdateAvailableSpace(int available_space) {
  if (available_space_ == available_space) {
    return;
  }

  available_space_ = available_space;
  Relayout(RelayoutReason::kAvailableSpaceChanged);
}

void BirchBarView::SetRelayoutCallback(RelayoutCallback callback) {
  CHECK(!relayout_callback_);
  relayout_callback_ = std::move(callback);
}

int BirchBarView::GetChipsNum() const {
  return chips_.size();
}

void BirchBarView::SetupChips(const std::vector<raw_ptr<BirchItem>>& items) {
  // Do not setup on shutting down.
  if (state_ == State::kShuttingDown) {
    return;
  }

  // The layer may be performing fading out animation while reloading.
  auto* animator = layer()->GetAnimator();
  if (animator->is_animating()) {
    animator->AbortAllAnimations();
  }

  // Clear current chips.
  Clear();

  for (auto item : items) {
    chips_.emplace_back(
        primary_row_->AddChildView(views::Builder<BirchChipButton>()
                                       .Init(item)
                                       .SetPreferredSize(chip_size_)
                                       .Build()));
  }

  RelayoutReason reason = RelayoutReason::kAddRemoveChip;
  switch (state_) {
    case State::kLoading:
    case State::kNormal:
      reason = RelayoutReason::kSetup;
      break;
    case State::kLoadingByUser:
      reason = RelayoutReason::kSetupByUser;
      break;
    // When loading for informed restore or reloading, directly perform fading
    // in animation since the bar was filled by chip loaders.
    case State::kLoadingForInformedRestore:
    case State::kReloading:
      break;
    case State::kShuttingDown:
      NOTREACHED() << "Birch bar cannot be setup while shutting down.";
  }

  // Change relayout reason to setup if new chips are filled in the empty bar.
  Relayout(reason);

  // Perform fade-in animation.
  FadeInChips();
}

void BirchBarView::AddChip(BirchItem* item) {
  if (static_cast<int>(chips_.size()) == kMaxChipsNum) {
    NOTREACHED() << "The number of birch chips reaches the limit of 4";
  }

  auto chip = views::Builder<BirchChipButton>()
                  .Init(item)
                  .SetPreferredSize(chip_size_)
                  .Build();
  AttachChip(std::move(chip));
}

void BirchBarView::RemoveChip(BirchItem* removed_item,
                              BirchItem* attached_item) {
  auto iter = std::find_if(chips_.begin(), chips_.end(),
                           [removed_item](BirchChipButtonBase* chip) {
                             return chip->GetItem() == removed_item;
                           });

  if (iter == chips_.end()) {
    return;
  }

  BirchChipButtonBase* removing_chip = *iter;
  chips_.erase(iter);

  // Shut down the chip to avoid dangling ptr.
  removing_chip->Shutdown();

  // The removing chip should stop processing events and not be focusable.
  removing_chip->SetCanProcessEventsWithinSubtree(false);
  removing_chip->SetFocusBehavior(views::View::FocusBehavior::NEVER);

  // Create a new chip for the attached item.
  if (attached_item) {
    chip_to_attach_ = views::Builder<BirchChipButton>()
                          .Init(attached_item)
                          .SetPreferredSize(chip_size_)
                          .Build();
  }

  // Apply fading-out animation to the chip being removed.
  views::AnimationBuilder fade_out_animation;
  fade_out_animation
      .SetPreemptionStrategy(
          ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET)
      .OnEnded(base::BindOnce(&BirchBarView::OnRemovingChipFadeOutEnded,
                              base::Unretained(this), removing_chip))
      .Once()
      .SetDuration(kFadeOutChipDurationOnRemoving)
      .SetOpacity(removing_chip->layer(), 0.0f);
}

void BirchBarView::UpdateChip(BirchItem* item) {
  auto iter = std::find_if(
      chips_.begin(), chips_.end(),
      [item](BirchChipButtonBase* chip) { return chip->GetItem() == item; });

  if (iter == chips_.end()) {
    return;
  }

  (*iter)->Init(item);
}

int BirchBarView::GetMaximumHeight() const {
  return GetExpectedLayoutType(kMaxChipsNum) == LayoutType::kOneByFour
             ? kChipHeight
             : 2 * kChipHeight + kChipSpacing;
}

bool BirchBarView::IsAnimating() {
  // Check if there are any layer animations in queue.
  if (layer()->GetAnimator()->is_animating()) {
    return true;
  }

  for (const auto& chip : chips_) {
    if (chip->layer()->GetAnimator()->is_animating()) {
      return true;
    }
  }

  return false;
}

void BirchBarView::AttachChip(std::unique_ptr<BirchChipButtonBase> chip) {
  auto* chip_layer = chip->layer();
  chip_layer->SetOpacity(0.0f);

  // Attach the chip to the secondary row if it is not empty, otherwise, to the
  // primary row.
  const bool attach_to_primary = !secondary_row_;
  chips_.emplace_back((attach_to_primary ? primary_row_ : secondary_row_)
                          ->AddChildView(std::move(chip)));
  Relayout(RelayoutReason::kAddRemoveChip);

  if (attach_to_primary) {
    // Perform sliding-in and fading-in animation if the chip was originally
    // attached to the primary row.
    chip_layer->SetTransform(
        gfx::Transform::MakeTranslation(kChipSpacing + chip_size_.width(), 0));
    views::AnimationBuilder sliding_fading_in_animation;
    sliding_fading_in_animation
        .SetPreemptionStrategy(
            ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET)
        .Once()
        .At(kSlidingChipDelayOnAttachment)
        .SetDuration(kSlidingChipDurationOnAttachment)
        .SetTransform(chip_layer, gfx::Transform(),
                      gfx::Tween::ACCEL_LIN_DECEL_100_3)
        .At(kFadeInChipDelayOnAttachment)
        .SetDuration(kFadeInChipDurationOnAttachment)
        .SetOpacity(chip_layer, 1.0f);
  } else {
    // TODO(zxdan): implement the animation when the motion spec is ready.
    chip_layer->SetOpacity(1.0f);
  }
}

void BirchBarView::Clear() {
  chips_.clear();
  primary_row_->RemoveAllChildViews();
  if (secondary_row_) {
    auto secondary_row = RemoveChildViewT(secondary_row_);
    secondary_row_ = nullptr;
  }

  chip_to_attach_.reset();

  if (state_ == State::kShuttingDown) {
    Relayout(RelayoutReason::kClearOnDisabled);
  }
}

gfx::Size BirchBarView::GetChipSize(aura::Window* root_window) const {
  const gfx::Rect display_bounds = display::Screen::GetScreen()
                                       ->GetDisplayNearestWindow(root_window)
                                       .bounds();
  // Always use the longest side of the display to calculate the chip width.
  const int max_display_dim =
      std::max(display_bounds.width(), display_bounds.height());

  // When in a large screen, the optimal chip width is used.
  if (max_display_dim > kLargeScreenThreshold) {
    return gfx::Size(kOptimalChipWidth, kChipHeight);
  }

  // Otherwise, the bar tends to fill the longest side of the display with 4
  // chips.
  const ShelfAlignment shelf_alignment =
      Shelf::ForWindow(root_window)->alignment();

  const int left_inset = shelf_alignment == ShelfAlignment::kLeft
                             ? kContainerHorizontalPaddingWithShelf
                             : kContainerHorizontalPaddingNoShelf;
  const int right_inset = shelf_alignment == ShelfAlignment::kRight
                              ? kContainerHorizontalPaddingWithShelf
                              : kContainerHorizontalPaddingNoShelf;
  const int chip_width =
      GetChipSpace(max_display_dim - left_inset - right_inset, kMaxChipsNum);
  return gfx::Size(chip_width, kChipHeight);
}

BirchBarView::LayoutType BirchBarView::GetExpectedLayoutType(
    int chip_num) const {
  // Calculate the expected layout type according to the chip space estimated by
  // current available space and number of chips.
  const int chip_space = GetChipSpace(available_space_, chip_num);
  return chip_space < chip_size_.width() ? LayoutType::kTwoByTwo
                                         : LayoutType::kOneByFour;
}

void BirchBarView::Relayout(RelayoutReason reason) {
  absl::Cleanup scoped_on_relayout = [this, reason] { OnRelayout(reason); };

  const size_t primary_size =
      GetExpectedLayoutType(chips_.size()) == LayoutType::kOneByFour
          ? kRowCapacityOf1x4Layout
          : kRowCapacityOf2x2Layout;

  // Create a secondary row for 2x2 layout if there is no secondary row.
  if (primary_size == kRowCapacityOf2x2Layout && !secondary_row_) {
    secondary_row_ = AddChildView(CreateChipsRow());
  }

  // Pop the extra chips from the end of the primary row and push to the head of
  // the secondary row.
  const views::View::Views& chips_in_primary = primary_row_->children();
  while (chips_in_primary.size() > primary_size) {
    secondary_row_->AddChildViewAt(
        primary_row_->RemoveChildViewT(chips_in_primary.back()), 0);
  }

  if (!secondary_row_) {
    return;
  }

  // Pop the chips from the head of the secondary row to the end of the primary
  // row if it still has available space.
  const views::View::Views& chips_in_secondary = secondary_row_->children();
  while (chips_in_primary.size() < primary_size &&
         !chips_in_secondary.empty()) {
    primary_row_->AddChildView(
        secondary_row_->RemoveChildViewT(chips_in_secondary.front()));
  }

  // Remove the secondary row if it is empty.
  if (chips_in_secondary.empty()) {
    auto secondary_row = RemoveChildViewT(secondary_row_);
    secondary_row_ = nullptr;
  }
}

void BirchBarView::OnRelayout(RelayoutReason reason) {
  InvalidateLayout();
  if (relayout_callback_) {
    relayout_callback_.Run(reason);
  }
}

void BirchBarView::AddLoadingChips() {
  CHECK(chips_.empty());

  // Add chip loaders to show loading animation.
  views::AnimationBuilder loading_animation;
  for (const base::TimeDelta& delay : kLoaderAnimationDelays) {
    auto* chip_loader = primary_row_->AddChildView(
        views::Builder<BirchChipLoaderView>()
            .SetPreferredSize(chip_size_)
            .SetDelay(delay)
            .SetType(BirchChipLoaderView::Type::kInit)
            .Build());
    chip_loader->AddAnimationToBuilder(loading_animation);
    chips_.emplace_back(chip_loader);
  }

  Relayout(RelayoutReason::kAddRemoveChip);
}

void BirchBarView::AddReloadingChips() {
  // The layer may be performing fading out animation while reloading.
  auto* animator = layer()->GetAnimator();
  if (animator->is_animating()) {
    animator->AbortAllAnimations();
  }

  const size_t chip_num = chips_.size() ? chips_.size() : kMaxChipsNum;

  // Clear the old chips and add the loader chips.
  Clear();

  // The bar may just fade out.
  layer()->SetOpacity(1.0f);

  views::AnimationBuilder reloading_animation;

  for (size_t i = 0; i < chip_num; i++) {
    auto* chip_loader = primary_row_->AddChildView(
        views::Builder<BirchChipLoaderView>()
            .SetPreferredSize(chip_size_)
            .SetDelay(kReloaderAnimationDelays[i])
            .SetType(BirchChipLoaderView::Type::kReload)
            .Build());
    chip_loader->AddAnimationToBuilder(reloading_animation);
    chips_.emplace_back(chip_loader);
  }

  Relayout(RelayoutReason::kAddRemoveChip);
}

void BirchBarView::FadeInChips() {
  if (!chips_.size()) {
    return;
  }

  layer()->SetOpacity(0.0f);

  // Perform fade-in animation.
  base::TimeDelta animation_delay;
  base::TimeDelta animation_duration;
  switch (state_) {
    case State::kLoadingForInformedRestore:
      animation_duration = kFadeInDurationAfterLoadingForInformedRestore;
      break;
    case State::kLoadingByUser:
      animation_delay = kFadeInDelayAfterLoadingByUser;
      animation_duration = kFadeInDurationAfterLoadingByUser;
      break;
    case State::kLoading:
    case State::kNormal:
      animation_delay = kFadeInDelayAfterLoading;
      animation_duration = kFadeInDurationAfterLoading;
      break;
    case State::kReloading:
      animation_duration = kFadeInDurationAfterReloading;
      break;
    case State::kShuttingDown:
      NOTREACHED() << "Birch bar cannot fade in while shutting down.";
  }

  views::AnimationBuilder animation_builder;
  animation_builder
      .SetPreemptionStrategy(
          ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET)
      .OnEnded(
          base::BindOnce(&BirchBarView::OnSetupEnded, base::Unretained(this)))
      .Once()
      .At(animation_delay)
      .SetDuration(animation_duration)
      .SetOpacity(layer(), 1.0f);
}

void BirchBarView::FadeOutChips() {
  base::TimeDelta animation_duration;
  base::OnceClosure animation_callback;
  switch (state_) {
    case State::kReloading:
      animation_duration = kFadeOutChipsDurationBeforeReloading;
      animation_callback = base::BindOnce(&BirchBarView::AddReloadingChips,
                                          base::Unretained(this));
      break;
    case State::kShuttingDown:
      animation_duration = kFadeOutChipsDurationOnHidingByUser;
      animation_callback =
          base::BindOnce(&BirchBarView::Clear, base::Unretained(this));
      break;
    case State::kLoadingForInformedRestore:
    case State::kLoadingByUser:
    case State::kLoading:
    case State::kNormal:
      NOTREACHED() << "Birch bar only fades out on shutting down and reloading";
  }

  if (!chips_.size()) {
    CHECK(animation_callback);
    std::move(animation_callback).Run();
    return;
  }

  views::AnimationBuilder fade_out_animation;
  fade_out_animation
      .SetPreemptionStrategy(
          ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET)
      .OnAborted(base::BindOnce(&BirchBarView::OnFadeOutAborted,
                                base::Unretained(this)))
      .OnEnded(std::move(animation_callback))
      .Once()
      .SetDuration(animation_duration)
      .SetOpacity(layer(), 0.0f);
}

void BirchBarView::OnFadeOutAborted() {
  layer()->SetOpacity(1.0f);
}

void BirchBarView::OnSetupEnded() {
  if (state_ == State::kLoading ||
      state_ == State::kLoadingForInformedRestore) {
    // Loading is finished, so possibly show a privacy nudge.
    MaybeShowPrivacyNudge();
  }
  SetState(State::kNormal);
}

void BirchBarView::OnRemovingChipFadeOutEnded(
    BirchChipButtonBase* removing_chip) {
  const LayoutType previous_layout_type =
      secondary_row_ ? LayoutType::kTwoByTwo : LayoutType::kOneByFour;

  switch (previous_layout_type) {
    case LayoutType::kOneByFour:
      RemoveChipFromOneRowBar(removing_chip);
      break;
    case LayoutType::kTwoByTwo:
      RemoveChipFromTwoRowsBar(removing_chip);
      break;
  }
}

void BirchBarView::RemoveChipFromOneRowBar(BirchChipButtonBase* removing_chip) {
  // Cache the old chips' bounds for animation.
  base::flat_map<BirchChipButtonBase*, gfx::Rect> old_chip_bounds;
  for (const auto& chip : chips_) {
    old_chip_bounds[chip] = chip->GetBoundsInScreen();
  }

  // Remove the chip from its owner.
  removing_chip->parent()->RemoveChildViewT(removing_chip);

  if (chip_to_attach_) {
    AttachChip(std::move(chip_to_attach_));
    // Attaching a chip after removing will not change the bar widget bounds
    // such that chips bounds will not get updated immediately. However, to
    // perform sliding animation, we need to get the chips target bounds to
    // calculate the transform. Here, we manually set bounds to the bar view to
    // trigger layout.
    SizeToPreferredSize();
  } else {
    Relayout(RelayoutReason::kAddRemoveChip);
  }

  // Apply sliding animations to the remaining chips.
  for (auto& chip_bounds : old_chip_bounds) {
    auto* chip = chip_bounds.first;
    chip->layer()->SetTransform(gfx::TransformBetweenRects(
        gfx::RectF(chip->GetBoundsInScreen()), gfx::RectF(chip_bounds.second)));
  }

  views::AnimationBuilder sliding_animations;
  sliding_animations.SetPreemptionStrategy(
      ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET);

  for (auto& chip : chips_) {
    sliding_animations.Once()
        .At(kSlidingChipsDelayOnRemoving)
        .SetDuration(kSlidingChipsDurationOnRemoving)
        .SetTransform(chip->layer(), gfx::Transform(),
                      gfx::Tween::Type::ACCEL_LIN_DECEL_100_3);
  }
}

void BirchBarView::RemoveChipFromTwoRowsBar(
    BirchChipButtonBase* removing_chip) {
  // TODO(zxdan): implement the animation when the motion spec is ready.
  removing_chip->parent()->RemoveChildViewT(removing_chip);
  if (chip_to_attach_) {
    AttachChip(std::move(chip_to_attach_));
  } else {
    Relayout(RelayoutReason::kAddRemoveChip);
  }
}

void BirchBarView::MaybeShowPrivacyNudge() {
  if (chips_.empty()) {
    return;
  }
  // The nudge is anchored on the first suggestion chip.
  views::View* anchor_view = chips_[0];
  Shell::Get()->birch_privacy_nudge_controller()->MaybeShowNudge(anchor_view);
}

BEGIN_METADATA(BirchBarView)
END_METADATA

}  // namespace ash