chromium/ash/shelf/shelf.cc

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

#include <memory>

#include "ash/animation/animation_change_type.h"
#include "ash/app_list/app_list_controller_impl.h"
#include "ash/constants/ash_constants.h"
#include "ash/constants/ash_features.h"
#include "ash/constants/ash_switches.h"
#include "ash/metrics/login_unlock_throughput_recorder.h"
#include "ash/public/cpp/keyboard/keyboard_controller_observer.h"
#include "ash/public/cpp/shelf_item_delegate.h"
#include "ash/public/cpp/shelf_model.h"
#include "ash/public/cpp/shelf_types.h"
#include "ash/public/cpp/shell_window_ids.h"
#include "ash/root_window_controller.h"
#include "ash/shelf/desk_button_widget.h"
#include "ash/shelf/hotseat_widget.h"
#include "ash/shelf/login_shelf_widget.h"
#include "ash/shelf/scrollable_shelf_view.h"
#include "ash/shelf/shelf_controller.h"
#include "ash/shelf/shelf_focus_cycler.h"
#include "ash/shelf/shelf_layout_manager.h"
#include "ash/shelf/shelf_layout_manager_observer.h"
#include "ash/shelf/shelf_navigation_widget.h"
#include "ash/shelf/shelf_observer.h"
#include "ash/shelf/shelf_tooltip_manager.h"
#include "ash/shelf/shelf_widget.h"
#include "ash/shell.h"
#include "ash/system/status_area_widget.h"
#include "ash/wm/work_area_insets.h"
#include "base/check.h"
#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/i18n/rtl.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/weak_ptr.h"
#include "base/metrics/histogram_macros.h"
#include "base/notreached.h"
#include "ui/display/types/display_constants.h"
#include "ui/gfx/geometry/rect.h"

namespace ash {

namespace {

bool IsAppListBackground(ShelfBackgroundType background_type) {
  switch (background_type) {
    case ShelfBackgroundType::kHomeLauncher:
      return true;
    case ShelfBackgroundType::kDefaultBg:
    case ShelfBackgroundType::kMaximized:
    case ShelfBackgroundType::kOobe:
    case ShelfBackgroundType::kLogin:
    case ShelfBackgroundType::kLoginNonBlurredWallpaper:
    case ShelfBackgroundType::kOverview:
    case ShelfBackgroundType::kInApp:
      return false;
  }
}

bool IsBottomAlignment(ShelfAlignment alignment) {
  return alignment == ShelfAlignment::kBottom ||
         alignment == ShelfAlignment::kBottomLocked;
}

}  // namespace

// Records smoothness of bounds animations for the HotseatWidget.
class HotseatWidgetAnimationMetricsReporter {
 public:
  // The different kinds of hotseat elements.
  enum class HotseatElementType {
    // The Hotseat Widget.
    kWidget,
    // The Hotseat Widget's translucent background.
    kTranslucentBackground
  };

  explicit HotseatWidgetAnimationMetricsReporter(
      HotseatElementType hotseat_element)
      : hotseat_element_(hotseat_element) {}
  ~HotseatWidgetAnimationMetricsReporter() = default;

  void ReportSmoothness(HotseatState target_state, int smoothness) {
    switch (target_state) {
      case HotseatState::kShownClamshell:
      case HotseatState::kShownHomeLauncher:
        if (hotseat_element_ == HotseatElementType::kWidget) {
          UMA_HISTOGRAM_PERCENTAGE(
              "Ash.HotseatWidgetAnimation.Widget.AnimationSmoothness."
              "TransitionToShownHotseat",
              smoothness);
        } else {
          UMA_HISTOGRAM_PERCENTAGE(
              "Ash.HotseatWidgetAnimation.TranslucentBackground."
              "AnimationSmoothness.TransitionToShownHotseat",
              smoothness);
        }
        break;
      case HotseatState::kExtended:
        if (hotseat_element_ == HotseatElementType::kWidget) {
          UMA_HISTOGRAM_PERCENTAGE(
              "Ash.HotseatWidgetAnimation.Widget.AnimationSmoothness."
              "TransitionToExtendedHotseat",
              smoothness);
        } else {
          UMA_HISTOGRAM_PERCENTAGE(
              "Ash.HotseatWidgetAnimation.TranslucentBackground."
              "AnimationSmoothness.TransitionToExtendedHotseat",
              smoothness);
        }
        break;
      case HotseatState::kHidden:
        if (hotseat_element_ == HotseatElementType::kWidget) {
          UMA_HISTOGRAM_PERCENTAGE(
              "Ash.HotseatWidgetAnimation.Widget.AnimationSmoothness."
              "TransitionToHiddenHotseat",
              smoothness);
        } else {
          UMA_HISTOGRAM_PERCENTAGE(
              "Ash.HotseatWidgetAnimation.TranslucentBackground."
              "AnimationSmoothness.TransitionToHiddenHotseat",
              smoothness);
        }
        break;
      case HotseatState::kNone:
        NOTREACHED();
    }
  }

  metrics_util::ReportCallback GetReportCallback(HotseatState target_state) {
    DCHECK_NE(target_state, HotseatState::kNone);
    return metrics_util::ForSmoothnessV3(base::BindRepeating(
        &HotseatWidgetAnimationMetricsReporter::ReportSmoothness,
        weak_ptr_factory_.GetWeakPtr(), target_state));
  }

 private:
  // The element that is reporting an animation.
  const HotseatElementType hotseat_element_;

  base::WeakPtrFactory<HotseatWidgetAnimationMetricsReporter> weak_ptr_factory_{
      this};
};

// An animation metrics reporter for the shelf navigation widget.
class ASH_EXPORT NavigationWidgetAnimationMetricsReporter {
 public:
  NavigationWidgetAnimationMetricsReporter() = default;

  ~NavigationWidgetAnimationMetricsReporter() = default;

  NavigationWidgetAnimationMetricsReporter(
      const NavigationWidgetAnimationMetricsReporter&) = delete;
  NavigationWidgetAnimationMetricsReporter& operator=(
      const NavigationWidgetAnimationMetricsReporter&) = delete;

  void ReportSmoothness(HotseatState target_hotseat_state, int smoothness) {
    switch (target_hotseat_state) {
      case HotseatState::kShownClamshell:
      case HotseatState::kShownHomeLauncher:
        UMA_HISTOGRAM_PERCENTAGE(
            "Ash.NavigationWidget.Widget.AnimationSmoothness."
            "TransitionToShownHotseat",
            smoothness);
        break;
      case HotseatState::kExtended:
        UMA_HISTOGRAM_PERCENTAGE(
            "Ash.NavigationWidget.Widget.AnimationSmoothness."
            "TransitionToExtendedHotseat",
            smoothness);
        break;
      case HotseatState::kHidden:
        UMA_HISTOGRAM_PERCENTAGE(
            "Ash.NavigationWidget.Widget.AnimationSmoothness."
            "TransitionToHiddenHotseat",
            smoothness);
        break;
      case HotseatState::kNone:
        NOTREACHED();
    }
  }

  metrics_util::ReportCallback GetReportCallback(
      HotseatState target_hotseat_state) {
    DCHECK_NE(target_hotseat_state, HotseatState::kNone);
    return metrics_util::ForSmoothnessV3(base::BindRepeating(
        &NavigationWidgetAnimationMetricsReporter::ReportSmoothness,
        weak_ptr_factory_.GetWeakPtr(), target_hotseat_state));
  }

 private:
  base::WeakPtrFactory<NavigationWidgetAnimationMetricsReporter>
      weak_ptr_factory_{this};
};

// Shelf::AutoHideEventHandler -----------------------------------------------

// Forwards mouse and gesture events to ShelfLayoutManager for auto-hide.
class Shelf::AutoHideEventHandler : public ui::EventHandler {
 public:
  explicit AutoHideEventHandler(Shelf* shelf) : shelf_(shelf) {
    Shell::Get()->AddPreTargetHandler(this);
  }

  AutoHideEventHandler(const AutoHideEventHandler&) = delete;
  AutoHideEventHandler& operator=(const AutoHideEventHandler&) = delete;

  ~AutoHideEventHandler() override {
    Shell::Get()->RemovePreTargetHandler(this);
  }

  // ui::EventHandler:
  void OnMouseEvent(ui::MouseEvent* event) override {
    shelf_->shelf_layout_manager()->UpdateAutoHideForMouseEvent(
        event, static_cast<aura::Window*>(event->target()));
  }
  void OnGestureEvent(ui::GestureEvent* event) override {
    shelf_->shelf_layout_manager()->ProcessGestureEventOfAutoHideShelf(
        event, static_cast<aura::Window*>(event->target()));
  }
  void OnTouchEvent(ui::TouchEvent* event) override {
    if (shelf_->auto_hide_behavior() != ShelfAutoHideBehavior::kAlways)
      return;

    // The event target should be the shelf widget or the hotseat widget.
    if (!shelf_->shelf_layout_manager()->IsShelfWindow(
            static_cast<aura::Window*>(event->target()))) {
      return;
    }

    // The touch-pressing event may hide the shelf. Lock the shelf's auto hide
    // state to give the shelf a chance to handle the touch event before it
    // being hidden.
    ShelfLayoutManager* shelf_layout_manager = shelf_->shelf_layout_manager();
    if (event->type() == ui::EventType::kTouchPressed && shelf_->IsVisible()) {
      shelf_layout_manager->LockAutoHideState(true);
    } else if (event->type() == ui::EventType::kTouchReleased ||
               event->type() == ui::EventType::kTouchCancelled) {
      // Unlock auto hide (and eventually recompute auto hide state).
      shelf_layout_manager->LockAutoHideState(false);
    }
  }

 private:
  raw_ptr<Shelf> shelf_;
};

// Shelf::AutoDimEventHandler -----------------------------------------------

// Handles mouse and touch events and determines whether ShelfLayoutManager
// should update shelf opacity for auto-dimming.
class Shelf::AutoDimEventHandler : public ui::EventHandler,
                                   public ShelfObserver {
 public:
  explicit AutoDimEventHandler(Shelf* shelf) : shelf_(shelf) {
    Shell::Get()->AddPreTargetHandler(this);
    shelf_observation_.Observe(shelf_.get());
    UndimShelf();
  }

  AutoDimEventHandler(const AutoDimEventHandler&) = delete;
  AutoDimEventHandler& operator=(const AutoDimEventHandler&) = delete;

  ~AutoDimEventHandler() override {
    Shell::Get()->RemovePreTargetHandler(this);
  }

  // ui::EventHandler:
  void OnMouseEvent(ui::MouseEvent* event) override {
    if (shelf_->shelf_layout_manager()->IsShelfWindow(
            static_cast<aura::Window*>(event->target()))) {
      UndimShelf();
    }
  }

  void OnTouchEvent(ui::TouchEvent* event) override {
    if (shelf_->shelf_layout_manager()->IsShelfWindow(
            static_cast<aura::Window*>(event->target()))) {
      UndimShelf();
    }
  }

  void StartDimShelfTimer() {
    dim_shelf_timer_.Start(
        FROM_HERE, kDimDelay,
        base::BindOnce(&AutoDimEventHandler::DimShelf, base::Unretained(this)));
  }

  void DimShelf() {
    // Attempt to dim the shelf. Stop the |dim_shelf_timer_| if successful.
    if (shelf_->shelf_layout_manager()->SetDimmed(true))
      dim_shelf_timer_.Stop();
  }

  // Sets shelf as active and sets timer to mark shelf as inactive.
  void UndimShelf() {
    shelf_->shelf_layout_manager()->SetDimmed(false);
    StartDimShelfTimer();
  }

  bool HasDimShelfTimer() { return dim_shelf_timer_.IsRunning(); }

  // ShelfObserver:
  void OnAutoHideStateChanged(ShelfAutoHideState new_state) override {
    // Shelf should be undimmed when it is shown.
    if (new_state == ShelfAutoHideState::SHELF_AUTO_HIDE_SHOWN)
      UndimShelf();
  }

  // ShelfObserver:
  void OnShelfVisibilityStateChanged(ShelfVisibilityState new_state) override {
    // Shelf should be undimmed when it is shown.
    if (new_state != ShelfVisibilityState::SHELF_HIDDEN)
      UndimShelf();
  }

 private:
  // Unowned pointer to the shelf that owns this event handler.
  raw_ptr<Shelf> shelf_;
  // OneShotTimer that dims shelf due to inactivity.
  base::OneShotTimer dim_shelf_timer_;
  // An observer that notifies the AutoDimHandler that shelf visibility has
  // changed.
  base::ScopedObservation<Shelf, ShelfObserver> shelf_observation_{this};

  // Delay before dimming the shelf.
  const base::TimeDelta kDimDelay = base::Seconds(5);
};

// Shelf::ScopedDisableAutoHide ----------------------------------------------

Shelf::ScopedDisableAutoHide::ScopedDisableAutoHide(Shelf* shelf)
    : shelf_(shelf->GetWeakPtr()) {
  CHECK(shelf);

  ++shelf_->disable_auto_hide_;
  if (shelf_->disable_auto_hide_ == 1) {
    shelf_->UpdateVisibilityState();
  }
}

Shelf::ScopedDisableAutoHide::~ScopedDisableAutoHide() {
  if (!shelf_) {
    return;
  }

  --shelf_->disable_auto_hide_;
  CHECK_GE(shelf_->disable_auto_hide_, 0);
  if (shelf_->disable_auto_hide_ == 0) {
    shelf_->UpdateVisibilityState();
  }
}

// Shelf ---------------------------------------------------------------------

Shelf::Shelf()
    : shelf_locking_manager_(this),
      shelf_focus_cycler_(std::make_unique<ShelfFocusCycler>(this)),
      tooltip_(std::make_unique<ShelfTooltipManager>(this)) {}

Shelf::~Shelf() = default;

// static
Shelf* Shelf::ForWindow(aura::Window* window) {
  return RootWindowController::ForWindow(window)->shelf();
}

// static
void Shelf::LaunchShelfItem(int item_index) {
  const int item_count = ShelfModel::Get()->item_count();

  // A negative argument will launch the last app. A positive argument will
  // launch the app at the corresponding index, unless it's higher than the
  // total number of apps, in which case we do nothing.
  if (item_index >= item_count)
    return;

  const int found_index = item_index >= 0 ? item_index : item_count - 1;

  // Set this one as active (or advance to the next item of its kind).
  ActivateShelfItem(found_index);
}

// static
void Shelf::ActivateShelfItem(int item_index) {
  ActivateShelfItemOnDisplay(item_index, display::kInvalidDisplayId);
}

// static
void Shelf::ActivateShelfItemOnDisplay(int item_index, int64_t display_id) {
  const ShelfModel* shelf_model = ShelfModel::Get();
  const ShelfItem& item = shelf_model->items()[item_index];
  ShelfItemDelegate* item_delegate = shelf_model->GetShelfItemDelegate(item.id);
  std::unique_ptr<ui::Event> event = std::make_unique<ui::KeyEvent>(
      ui::EventType::kKeyReleased, ui::VKEY_UNKNOWN, ui::EF_NONE);
  item_delegate->ItemSelected(std::move(event), display_id, LAUNCH_FROM_SHELF,
                              base::DoNothing(), base::NullCallback());
}

// static
void Shelf::UpdateShelfVisibility() {
  for (aura::Window* root : Shell::Get()->GetAllRootWindows()) {
    Shelf::ForWindow(root)->UpdateVisibilityState();
  }
}

void Shelf::CreateNavigationWidget(aura::Window* container) {
  DCHECK(container);
  DCHECK(!navigation_widget_);
  navigation_widget_ = std::make_unique<ShelfNavigationWidget>(
      this, hotseat_widget()->GetShelfView());
  navigation_widget_->Initialize(container);
  navigation_widget_metrics_reporter_ =
      std::make_unique<NavigationWidgetAnimationMetricsReporter>();
}

void Shelf::CreateDeskButtonWidget(aura::Window* container) {
  CHECK(container);
  CHECK(!desk_button_widget_);
  CHECK(ash::features::IsDeskButtonEnabled());

  desk_button_widget_ = std::make_unique<DeskButtonWidget>(this);
  desk_button_widget_->Initialize(container);
}

void Shelf::CreateHotseatWidget(aura::Window* container) {
  DCHECK(container);
  DCHECK(!hotseat_widget_);
  hotseat_widget_ = std::make_unique<HotseatWidget>();
  translucent_background_metrics_reporter_ =
      std::make_unique<HotseatWidgetAnimationMetricsReporter>(
          HotseatWidgetAnimationMetricsReporter::HotseatElementType::
              kTranslucentBackground);
  hotseat_widget_->Initialize(container, this);
  shelf_widget_->RegisterHotseatWidget(hotseat_widget());
  hotseat_transition_metrics_reporter_ =
      std::make_unique<HotseatWidgetAnimationMetricsReporter>(
          HotseatWidgetAnimationMetricsReporter::HotseatElementType::kWidget);
}

void Shelf::CreateStatusAreaWidget(aura::Window* shelf_container) {
  DCHECK(shelf_container);
  DCHECK(!status_area_widget_);
  status_area_widget_ =
      std::make_unique<StatusAreaWidget>(shelf_container, this);
  status_area_widget_->Initialize();
}

void Shelf::CreateShelfWidget(aura::Window* root) {
  DCHECK(!shelf_widget_);
  aura::Window* shelf_container =
      root->GetChildById(kShellWindowId_ShelfContainer);
  shelf_widget_ = std::make_unique<ShelfWidget>(this);

  DCHECK(!shelf_layout_manager_);
  shelf_layout_manager_ = shelf_widget_->shelf_layout_manager();
  shelf_layout_manager_->AddObserver(this);

  // Create the various shelf components.
  CreateHotseatWidget(shelf_container);
  CreateNavigationWidget(shelf_container);
  if (ash::features::IsDeskButtonEnabled()) {
    CreateDeskButtonWidget(shelf_container);
  }
  login_shelf_widget_ =
      std::make_unique<LoginShelfWidget>(/*shelf=*/this, shelf_container);

  // Must occur after |shelf_widget_| is constructed because the system tray
  // constructors call back into Shelf::shelf_widget().
  CreateStatusAreaWidget(shelf_container);
  shelf_widget_->Initialize(shelf_container);
  shelf_widget_->GetNativeWindow()->parent()->StackChildAtBottom(
      shelf_widget_->GetNativeWindow());

  // The Hotseat should be above everything in the shelf.
  hotseat_widget()->StackAtTop();
}

void Shelf::ShutdownShelfWidget() {
  for (auto& observer : observers_)
    observer.OnShelfShuttingDown();

  // Remove observers prior to destroying child widgets, this prevents
  // activation changes from triggering during shutdown, see
  // https://crbug.com/1307898.
  shelf_widget_->Shutdown();

  // The contents view of the hotseat widget may rely on the status area widget.
  // So do explicit destruction here.
  hotseat_widget_.reset();
  status_area_widget_.reset();
  navigation_widget_.reset();
  login_shelf_widget_.reset();
}

void Shelf::DestroyShelfWidget() {
  DCHECK(shelf_widget_);
  shelf_widget_.reset();
}

bool Shelf::IsVisible() const {
  return shelf_layout_manager_->IsVisible();
}

const aura::Window* Shelf::GetWindow() const {
  return shelf_widget_ ? shelf_widget_->GetNativeWindow() : nullptr;
}

aura::Window* Shelf::GetWindow() {
  return const_cast<aura::Window*>(const_cast<const Shelf*>(this)->GetWindow());
}

void Shelf::SetAlignment(ShelfAlignment alignment) {
  if (!shelf_widget_)
    return;

  if (alignment_ == alignment)
    return;

  if (shelf_locking_manager_.is_locked() &&
      alignment != ShelfAlignment::kBottomLocked) {
    shelf_locking_manager_.set_in_session_alignment(alignment);
    return;
  }

  bool needs_relayout =
      !IsBottomAlignment(alignment_) || !IsBottomAlignment(alignment);

  ShelfAlignment old_alignment = alignment_;
  alignment_ = alignment;
  tooltip_->Close();
  if (needs_relayout) {
    shelf_layout_manager_->HandleShelfAlignmentChange();
    Shell::Get()->NotifyShelfAlignmentChanged(GetWindow()->GetRootWindow(),
                                              old_alignment);
  }
}

bool IsHorizontalAlignment(ShelfAlignment alignment) {
  switch (alignment) {
    case ShelfAlignment::kBottom:
    case ShelfAlignment::kBottomLocked:
      return true;
    case ShelfAlignment::kLeft:
    case ShelfAlignment::kRight:
      return false;
  }
  NOTREACHED();
}

bool Shelf::IsHorizontalAlignment() const {
  return ash::IsHorizontalAlignment(alignment_);
}

void Shelf::SetAutoHideBehavior(ShelfAutoHideBehavior auto_hide_behavior) {
  DCHECK(shelf_layout_manager_);

  if (auto_hide_behavior_ == auto_hide_behavior)
    return;

  auto_hide_behavior_ = auto_hide_behavior;

  for (auto& observer : observers_)
    observer.OnShelfAutoHideBehaviorChanged();
}

ShelfAutoHideState Shelf::GetAutoHideState() const {
  return shelf_layout_manager_->auto_hide_state();
}

void Shelf::UpdateAutoHideState() {
  shelf_layout_manager_->UpdateAutoHideState();
}

ShelfBackgroundType Shelf::GetBackgroundType() const {
  return shelf_layout_manager_ ? shelf_layout_manager_->shelf_background_type()
                               : ShelfBackgroundType::kDefaultBg;
}

void Shelf::UpdateVisibilityState() {
  if (shelf_layout_manager_)
    shelf_layout_manager_->UpdateVisibilityState(/*force_layout=*/false);
}

void Shelf::MaybeUpdateShelfBackground() {
  if (!shelf_layout_manager_)
    return;

  shelf_layout_manager_->MaybeUpdateShelfBackground(
      AnimationChangeType::ANIMATE);
}

ShelfVisibilityState Shelf::GetVisibilityState() const {
  return shelf_layout_manager_ ? shelf_layout_manager_->visibility_state()
                               : SHELF_HIDDEN;
}

gfx::Rect Shelf::GetShelfBoundsInScreen() const {
  return shelf_widget()->GetTargetBounds();
}

gfx::Rect Shelf::GetIdealBounds() const {
  return shelf_layout_manager_->GetIdealBounds();
}

gfx::Rect Shelf::GetIdealBoundsForWorkAreaCalculation() {
  return shelf_layout_manager_->GetIdealBoundsForWorkAreaCalculation();
}

gfx::Rect Shelf::GetScreenBoundsOfItemIconForWindow(aura::Window* window) {
  if (!shelf_widget_)
    return gfx::Rect();
  return shelf_widget_->GetScreenBoundsOfItemIconForWindow(window);
}

bool Shelf::ProcessGestureEvent(const ui::GestureEvent& event) {
  // Can be called at login screen.
  if (!shelf_layout_manager_)
    return false;
  return shelf_layout_manager_->ProcessGestureEvent(event);
}

void Shelf::ProcessMouseEvent(const ui::MouseEvent& event) {
  if (shelf_layout_manager_)
    shelf_layout_manager_->ProcessMouseEventFromShelf(event);
}

void Shelf::ProcessScrollEvent(ui::ScrollEvent* event) {
  if (event->finger_count() != 2 || event->type() != ui::EventType::kScroll) {
    return;
  }

  if (!shelf_layout_manager_->is_active_session_state())
    return;

  // Introduce the swipe up gesture behind a flag over certain conditions.
  if (!shelf_layout_manager_->IsBubbleLauncherShowOnGestureScrollAvailable())
    return;

  auto* app_list_controller = Shell::Get()->app_list_controller();
  DCHECK(app_list_controller);

  shelf_layout_manager_->ProcessScrollEventFromShelf(event);
  event->SetHandled();
}

void Shelf::ProcessMouseWheelEvent(ui::MouseWheelEvent* event) {
  if (!shelf_layout_manager_->is_active_session_state() ||
      !IsHorizontalAlignment())
    return;

  // Introduce the swipe up gesture behind a flag over certain conditions.
  if (!shelf_layout_manager_->IsBubbleLauncherShowOnGestureScrollAvailable())
    return;

  auto* app_list_controller = Shell::Get()->app_list_controller();
  DCHECK(app_list_controller);

  shelf_layout_manager_->ProcessMouseWheelEventFromShelf(event);
  event->SetHandled();
}

void Shelf::AddObserver(ShelfObserver* observer) {
  observers_.AddObserver(observer);
}

void Shelf::RemoveObserver(ShelfObserver* observer) {
  observers_.RemoveObserver(observer);
}

void Shelf::NotifyShelfIconPositionsChanged() {
  for (auto& observer : observers_)
    observer.OnShelfIconPositionsChanged();
}

StatusAreaWidget* Shelf::GetStatusAreaWidget() const {
  return shelf_widget_ ? shelf_widget_->status_area_widget() : nullptr;
}

gfx::Rect Shelf::GetSystemTrayAnchorRect() const {
  gfx::Rect work_area = GetWorkAreaInsets()->user_work_area_bounds();
  switch (alignment_) {
    case ShelfAlignment::kBottom:
    case ShelfAlignment::kBottomLocked:
      return gfx::Rect(base::i18n::IsRTL()
                           ? work_area.x()
                           : work_area.right() - kShelfDisplayOffset,
                       work_area.bottom() - kShelfDisplayOffset, 0, 0);
    case ShelfAlignment::kLeft:
      return gfx::Rect(work_area.x(), work_area.bottom() - kShelfDisplayOffset,
                       0, 0);
    case ShelfAlignment::kRight:
      return gfx::Rect(work_area.right() - kShelfDisplayOffset,
                       work_area.bottom() - kShelfDisplayOffset, 0, 0);
  }
  NOTREACHED();
}

bool Shelf::ShouldHideOnSecondaryDisplay(session_manager::SessionState state) {
  if (Shell::GetPrimaryRootWindowController()->shelf() == this)
    return false;

  return state != session_manager::SessionState::ACTIVE;
}

void Shelf::SetVirtualKeyboardBoundsForTesting(const gfx::Rect& bounds) {
  KeyboardStateDescriptor state;
  state.is_visible = !bounds.IsEmpty();
  state.visual_bounds = bounds;
  state.occluded_bounds_in_screen = bounds;
  state.displaced_bounds_in_screen = gfx::Rect();
  WorkAreaInsets* work_area_insets = GetWorkAreaInsets();
  work_area_insets->OnKeyboardVisibilityChanged(state.is_visible);
  work_area_insets->OnKeyboardVisibleBoundsChanged(state.visual_bounds);
  work_area_insets->OnKeyboardOccludedBoundsChanged(
      state.occluded_bounds_in_screen);
  work_area_insets->OnKeyboardDisplacingBoundsChanged(
      state.displaced_bounds_in_screen);
  work_area_insets->OnKeyboardAppearanceChanged(state);
}

ShelfLockingManager* Shelf::GetShelfLockingManagerForTesting() {
  return &shelf_locking_manager_;
}

ShelfView* Shelf::GetShelfViewForTesting() {
  return shelf_widget_->shelf_view_for_testing();
}

metrics_util::ReportCallback Shelf::GetHotseatTransitionReportCallback(
    HotseatState target_state) {
  return hotseat_transition_metrics_reporter_->GetReportCallback(target_state);
}

metrics_util::ReportCallback Shelf::GetTranslucentBackgroundReportCallback(
    HotseatState target_state) {
  return translucent_background_metrics_reporter_->GetReportCallback(
      target_state);
}

metrics_util::ReportCallback Shelf::GetNavigationWidgetAnimationReportCallback(
    HotseatState target_hotseat_state) {
  return navigation_widget_metrics_reporter_->GetReportCallback(
      target_hotseat_state);
}

void Shelf::WillDeleteShelfLayoutManager() {
  // Clear event handlers that might forward events to the destroyed instance.
  auto_hide_event_handler_.reset();
  auto_dim_event_handler_.reset();
  navigation_widget_metrics_reporter_.reset();

  DCHECK(shelf_layout_manager_);
  shelf_layout_manager_->RemoveObserver(this);
  shelf_layout_manager_ = nullptr;
}

void Shelf::OnShelfVisibilityStateChanged(ShelfVisibilityState new_state) {
  if (!auto_dim_event_handler_ && switches::IsUsingShelfAutoDim()) {
    auto_dim_event_handler_ = std::make_unique<AutoDimEventHandler>(this);
  }

  if (new_state != SHELF_AUTO_HIDE) {
    auto_hide_event_handler_.reset();
  } else if (!auto_hide_event_handler_) {
    auto_hide_event_handler_ = std::make_unique<AutoHideEventHandler>(this);
  }

  for (auto& observer : observers_) {
    observer.OnShelfVisibilityStateChanged(new_state);
  }
}

void Shelf::OnAutoHideStateChanged(ShelfAutoHideState new_state) {
  for (auto& observer : observers_)
    observer.OnAutoHideStateChanged(new_state);
}

void Shelf::OnBackgroundUpdated(ShelfBackgroundType background_type,
                                AnimationChangeType change_type) {
  // Shelf should undim when transitioning to show app list.
  if (auto_dim_event_handler_ && IsAppListBackground(background_type))
    UndimShelf();

  for (auto& observer : observers_)
    observer.OnBackgroundTypeChanged(background_type, change_type);
}

void Shelf::OnHotseatStateChanged(HotseatState old_state,
                                  HotseatState new_state) {
  for (auto& observer : observers_)
    observer.OnHotseatStateChanged(old_state, new_state);
}

void Shelf::OnWorkAreaInsetsChanged() {
  for (auto& observer : observers_)
    observer.OnShelfWorkAreaInsetsChanged();
}

void Shelf::DimShelf() {
  auto_dim_event_handler_->DimShelf();
}

void Shelf::UndimShelf() {
  auto_dim_event_handler_->UndimShelf();
}

bool Shelf::HasDimShelfTimer() {
  return auto_dim_event_handler_->HasDimShelfTimer();
}

WorkAreaInsets* Shelf::GetWorkAreaInsets() const {
  const aura::Window* window = GetWindow();
  DCHECK(window);
  return WorkAreaInsets::ForWindow(window->GetRootWindow());
}

base::WeakPtr<Shelf> Shelf::GetWeakPtr() {
  return weak_ptr_factory_.GetWeakPtr();
}

}  // namespace ash