chromium/ash/wm/window_util.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_util.h"

#include <memory>
#include <tuple>

#include "ash/constants/ash_features.h"
#include "ash/constants/ash_pref_names.h"
#include "ash/multi_user/multi_user_window_manager_impl.h"
#include "ash/public/cpp/app_types_util.h"
#include "ash/public/cpp/input_device_settings_controller.h"
#include "ash/public/cpp/shell_window_ids.h"
#include "ash/public/cpp/window_properties.h"
#include "ash/public/mojom/input_device_settings.mojom.h"
#include "ash/root_window_controller.h"
#include "ash/screen_util.h"
#include "ash/session/session_controller_impl.h"
#include "ash/shelf/shelf.h"
#include "ash/shell.h"
#include "ash/shell_delegate.h"
#include "ash/wm/desks/desks_util.h"
#include "ash/wm/float/float_controller.h"
#include "ash/wm/mru_window_tracker.h"
#include "ash/wm/overview/overview_controller.h"
#include "ash/wm/overview/overview_grid.h"
#include "ash/wm/overview/overview_session.h"
#include "ash/wm/overview/overview_utils.h"
#include "ash/wm/snap_group/snap_group.h"
#include "ash/wm/snap_group/snap_group_controller.h"
#include "ash/wm/splitview/split_view_controller.h"
#include "ash/wm/splitview/split_view_overview_session.h"
#include "ash/wm/splitview/split_view_utils.h"
#include "ash/wm/window_positioning_utils.h"
#include "ash/wm/window_state.h"
#include "ash/wm/wm_constants.h"
#include "ash/wm/wm_event.h"
#include "base/containers/adapters.h"
#include "base/containers/contains.h"
#include "base/functional/bind.h"
#include "base/memory/raw_ptr.h"
#include "base/ranges/algorithm.h"
#include "chromeos/constants/chromeos_features.h"
#include "chromeos/ui/base/app_types.h"
#include "chromeos/ui/base/chromeos_ui_constants.h"
#include "chromeos/ui/base/window_properties.h"
#include "chromeos/ui/base/window_state_type.h"
#include "chromeos/ui/frame/caption_buttons/snap_controller.h"
#include "chromeos/ui/frame/interior_resize_handler_targeter.h"
#include "components/prefs/pref_registry_simple.h"
#include "components/prefs/pref_service.h"
#include "ui/aura/client/aura_constants.h"
#include "ui/aura/client/capture_client.h"
#include "ui/aura/client/focus_client.h"
#include "ui/aura/window.h"
#include "ui/aura/window_delegate.h"
#include "ui/aura/window_event_dispatcher.h"
#include "ui/aura/window_targeter.h"
#include "ui/base/hit_test.h"
#include "ui/base/mojom/ui_base_types.mojom-shared.h"
#include "ui/base/ui_base_types.h"
#include "ui/compositor/compositor.h"
#include "ui/compositor/layer.h"
#include "ui/compositor/layer_tree_owner.h"
#include "ui/display/display.h"
#include "ui/display/screen.h"
#include "ui/events/event.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/gfx/geometry/rounded_corners_f.h"
#include "ui/gfx/geometry/transform_util.h"
#include "ui/views/view.h"
#include "ui/views/widget/widget.h"
#include "ui/wm/core/coordinate_conversion.h"
#include "ui/wm/core/easy_resize_window_targeter.h"
#include "ui/wm/core/scoped_animation_disabler.h"
#include "ui/wm/core/window_animations.h"
#include "ui/wm/public/activation_client.h"

namespace ash::window_util {
namespace {

// Returns true if `window` has any descendant that is a system modal window or
// is itself a system modal window.
bool ContainsSystemModalWindow(const aura::Window* window) {
  if (!window) {
    return false;
  }

  if (window->GetProperty(aura::client::kModalKey) ==
      ui::mojom::ModalType::kSystem) {
    return true;
  }

  for (const aura::Window* child : window->children()) {
    if (ContainsSystemModalWindow(child)) {
      return true;
    }
  }

  return false;
}

// Returns the lowest common parent of the given `windows` by traversing up from
// one of the windows' direct parent and check if the intermediate parent
// contains all the `windows`. If yes, it will be the lowest common parent.
aura::Window* FindLowestCommonParent(const aura::Window::Windows& windows) {
  if (windows.empty()) {
    return nullptr;
  }

  auto contains_all = [&](aura::Window* parent) {
    for (aura::Window* window : windows) {
      if (!parent->Contains(window)) {
        return false;
      }
    }

    return true;
  };

  // As a window can `Contains` itself, which is not the common parent, we start
  // traversing from its parent instead.
  for (aura::Window* parent = windows.front()->parent(); parent;
       parent = parent->parent()) {
    if (contains_all(parent)) {
      return parent;
    }
  }

  return nullptr;
}

// Uses DFS to find the topmost child of the `parent` that is included in
// `windows`. With the reverse traversing of the children, the first observed
// window found will be the topmost one.
aura::Window* FindTopMostChild(aura::Window* parent,
                               const aura::Window::Windows& windows) {
  for (aura::Window* child : base::Reversed(parent->children())) {
    for (aura::Window* window : windows) {
      if (child == window && !window->is_destroying()) {
        return window;
      }
      if (child->Contains(window)) {
        return FindTopMostChild(child, windows);
      }
    }
  }

  return nullptr;
}

}  // namespace

int GetMiniWindowRoundedCornerRadius() {
  return chromeos::features::IsRoundedWindowsEnabled()
             ? chromeos::features::RoundedWindowsRadius()
             : kWindowMiniViewCornerRadius;
}

gfx::RoundedCornersF GetMiniWindowRoundedCorners(const aura::Window* window,
                                                 bool include_header_rounding,
                                                 std::optional<float> scale) {
  const int corner_radius = window_util::GetMiniWindowRoundedCornerRadius();
  const float scaled_corner_radius = corner_radius / scale.value_or(1.0f);

  if (SnapGroupController* snap_group_controller = SnapGroupController::Get()) {
    if (SnapGroup* snap_group =
            snap_group_controller->GetSnapGroupForGivenWindow(window)) {
      const bool is_in_horizontal_snap_group =
          snap_group->IsSnapGroupLayoutHorizontal();
      if (window == snap_group->GetPhysicallyLeftOrTopWindow()) {
        return is_in_horizontal_snap_group
                   ? gfx::RoundedCornersF(
                         /*upper_left=*/include_header_rounding
                             ? scaled_corner_radius
                             : 0,
                         /*upper_right=*/0, /*lower_right=*/0,
                         /*lower_left=*/scaled_corner_radius)
                   : gfx::RoundedCornersF(
                         /*upper_left=*/include_header_rounding
                             ? scaled_corner_radius
                             : 0,
                         /*upper_right=*/
                         include_header_rounding ? scaled_corner_radius : 0,
                         /*lower_right=*/0,
                         /*lower_left=*/0);
      }

      return is_in_horizontal_snap_group
                 ? gfx::RoundedCornersF(
                       /*upper_left=*/0,
                       /*upper_right=*/
                       include_header_rounding ? scaled_corner_radius : 0,
                       /*lower_right=*/
                       scaled_corner_radius,
                       /*lower_left=*/0)
                 : gfx::RoundedCornersF(
                       /*upper_left=*/0,
                       /*upper_right=*/0,
                       /*lower_right=*/
                       scaled_corner_radius,
                       /*lower_left=*/scaled_corner_radius);
    }
  }

  return gfx::RoundedCornersF(
      /*upper_left=*/include_header_rounding ? scaled_corner_radius : 0,
      /*upper_right=*/include_header_rounding ? scaled_corner_radius : 0,
      /*lower_right=*/scaled_corner_radius,
      /*lower_left=*/scaled_corner_radius);
}

aura::Window* GetActiveWindow() {
  if (auto* activation_client =
          wm::GetActivationClient(Shell::GetPrimaryRootWindow())) {
    return activation_client->GetActiveWindow();
  }
  return nullptr;
}

aura::Window* GetFocusedWindow() {
  return aura::client::GetFocusClient(Shell::GetPrimaryRootWindow())
      ->GetFocusedWindow();
}

bool IsStackedBelow(aura::Window* win1, aura::Window* win2) {
  CHECK_NE(win1, win2);
  CHECK_EQ(win1->parent(), win2->parent());

  const auto& children = win1->parent()->children();
  auto win1_iter = base::ranges::find(children, win1);
  auto win2_iter = base::ranges::find(children, win2);
  CHECK(win1_iter != children.end());
  CHECK(win2_iter != children.end());
  return win1_iter < win2_iter;
}

aura::Window* GetTopMostWindow(const aura::Window::Windows& windows) {
  aura::Window* lowest_common_parent = FindLowestCommonParent(windows);
  if (!lowest_common_parent) {
    return nullptr;
  }

  return FindTopMostChild(lowest_common_parent, windows);
}

std::vector<aura::Window*> SortWindowsBottomToTop(
    std::set<raw_ptr<aura::Window, SetExperimental>> window_set) {
  std::vector<aura::Window*> ordered;
  std::vector<aura::Window*> root_windows;
  std::stack<aura::Window*> stack;

  // Collect unique root windows and put them on the stack.
  for (aura::Window* window : window_set) {
    // The call to `GetRootWindow` here traverses up the window tree to the
    // root, so this is technically quadratic time in the worst case, but is
    // effectively linear time for shallow trees, which are common.
    root_windows.push_back(window->GetRootWindow());
  }
  for (auto* window : base::flat_set<aura::Window*>(std::move(root_windows))) {
    stack.push(window);
  }

  // Pre-order DFS from bottom-most to top-most windows.
  while (!stack.empty()) {
    auto* window = stack.top();
    stack.pop();

    if (window_set.erase(window)) {
      ordered.push_back(window);
    }

    // Push so bottom-most is on the top of the stack.
    for (aura::Window* child : base::Reversed(window->children())) {
      stack.push(child);
    }
  }

  return ordered;
}

aura::Window* GetCaptureWindow() {
  return aura::client::GetCaptureWindow(Shell::GetPrimaryRootWindow());
}

void GetBlockingContainersForRoot(aura::Window* root_window,
                                  aura::Window** min_container,
                                  aura::Window** system_modal_container) {
  if (Shell::Get()->session_controller()->IsUserSessionBlocked()) {
    *min_container =
        root_window->GetChildById(kShellWindowId_LockScreenContainersContainer);
    *system_modal_container =
        root_window->GetChildById(kShellWindowId_LockSystemModalContainer);
  } else if (aura::Window* const help_bubble_container =
                 root_window->GetChildById(kShellWindowId_HelpBubbleContainer);
             ContainsSystemModalWindow(help_bubble_container)) {
    *min_container = help_bubble_container;
    *system_modal_container = nullptr;
  } else {
    *min_container = nullptr;
    *system_modal_container =
        root_window->GetChildById(kShellWindowId_SystemModalContainer);
  }
}

bool IsWindowUserPositionable(aura::Window* window) {
  return window->GetType() == aura::client::WINDOW_TYPE_NORMAL;
}

void PinWindow(aura::Window* window, bool trusted) {
  WMEvent event(trusted ? WM_EVENT_TRUSTED_PIN : WM_EVENT_PIN);
  WindowState::Get(window)->OnWMEvent(&event);
}

void SetAutoHideShelf(aura::Window* window, bool autohide) {
  WindowState::Get(window)->set_autohide_shelf_when_maximized_or_fullscreen(
      autohide);
  for (aura::Window* root_window : Shell::GetAllRootWindows())
    Shelf::ForWindow(root_window)->UpdateVisibilityState();
}

bool MoveWindowToDisplay(aura::Window* window, int64_t display_id) {
  DCHECK(window);

  aura::Window* root = Shell::GetRootWindowForDisplayId(display_id);
  if (!root || root == window->GetRootWindow()) {
    NOTREACHED();
  }

  WindowState* window_state = WindowState::Get(window);
  if (window_state->allow_set_bounds_direct()) {
    display::Display display;
    if (!display::Screen::GetScreen()->GetDisplayWithDisplayId(display_id,
                                                               &display))
      return false;
    gfx::Rect bounds = window->bounds();
    gfx::Rect work_area_in_display(display.size());
    work_area_in_display.Inset(display.GetWorkAreaInsets());
    AdjustBoundsToEnsureMinimumWindowVisibility(
        work_area_in_display, /*client_controlled=*/false, &bounds);
    SetBoundsWMEvent event(bounds, display_id);
    window_state->OnWMEvent(&event);
    return true;
  }

  // Moves |window| to the given |root| window's corresponding container.
  aura::Window* container = RootWindowController::ForWindow(root)->GetContainer(
      window->parent()->GetId());
  if (!container)
    return false;

  // Update restore bounds to target root window.
  if (window_state->HasRestoreBounds()) {
    gfx::Rect restore_bounds = window_state->GetRestoreBoundsInParent();
    ::wm::ConvertRectToScreen(root, &restore_bounds);
    window_state->SetRestoreBoundsInScreen(restore_bounds);
  }

  container->AddChild(window);
  return true;
}

int GetNonClientComponent(aura::Window* window, const gfx::Point& location) {
  return window->delegate()
             ? window->delegate()->GetNonClientComponent(location)
             : HTNOWHERE;
}

void SetChildrenUseExtendedHitRegionForWindow(aura::Window* window) {
  gfx::Insets mouse_extend(-chromeos::kResizeOutsideBoundsSize);
  gfx::Insets touch_extend = gfx::ScaleToFlooredInsets(
      mouse_extend, chromeos::kResizeOutsideBoundsScaleForTouch);
  window->SetEventTargeter(std::make_unique<::wm::EasyResizeWindowTargeter>(
      mouse_extend, touch_extend));
}

void CloseWidgetForWindow(aura::Window* window) {
  views::Widget* widget = views::Widget::GetWidgetForNativeView(window);
  DCHECK(widget);
  widget->Close();
}

void InstallResizeHandleWindowTargeterForWindow(aura::Window* window) {
  window->SetEventTargeter(
      std::make_unique<chromeos::InteriorResizeHandleTargeter>(
          base::BindRepeating([](const aura::Window* window) {
            const WindowState* window_state = WindowState::Get(window);
            return window_state ? window_state->GetStateType()
                                : chromeos::WindowStateType::kDefault;
          })));
}

bool IsDraggingTabs(const aura::Window* window) {
  return window->GetProperty(ash::kIsDraggingTabsKey);
}

bool ShouldExcludeForCycleList(const aura::Window* window) {
  // Exclude windows:
  // - non user positionable windows, such as extension popups.
  // - windows being dragged
  // - pip windows
  const WindowState* state = WindowState::Get(window);
  if (!state->IsUserPositionable() || state->is_dragged() || state->IsPip())
    return true;

  // Exclude the AppList window, which will hide as soon as cycling starts
  // anyway. It doesn't make sense to count it as a "switchable" window, yet
  // a lot of code relies on the MRU list returning the app window. If we
  // don't manually remove it, the window cycling UI won't crash or misbehave,
  // but there will be a flicker as the target window changes. Also exclude
  // unselectable windows such as extension popups.
  for (auto* parent = window->parent(); parent; parent = parent->parent()) {
    if (parent->GetId() == kShellWindowId_AppListContainer)
      return true;
  }

  return window->GetProperty(kHideInOverviewKey);
}

bool ShouldExcludeForOverview(const aura::Window* window) {
  if (ShouldExcludeForCycleList(window)) {
    return true;
  }

  if (display::Screen::GetScreen()->InTabletMode()) {
    return window == SplitViewController::Get(window->GetRootWindow())
                         ->GetDefaultSnappedWindow();
  }

  // A window should be excluded from being shown in Overview in clamshell mode
  // when:
  // 1. The window itself is the snapped window in partial Overview;
  SplitViewController* split_view_controller = SplitViewController::Get(window);
  SplitViewController::State split_view_state = split_view_controller->state();
  const bool in_partial_overview =
      split_view_state == SplitViewController::State::kPrimarySnapped ||
      split_view_state == SplitViewController::State::kSecondarySnapped;
  if (in_partial_overview &&
      window == split_view_controller->GetDefaultSnappedWindow()) {
    return true;
  }

  // 2. The given `window` or its transient parent is not the most recently used
  // (MRU) window within its snap group i.e. the corresponding Overview item
  // representation for the snap group has been created. Note that the
  // activatable transient window is included in the window cycle list
  if (SnapGroupController* snap_group_controller = SnapGroupController::Get()) {
    SnapGroup* snap_group =
        snap_group_controller->GetSnapGroupForGivenWindow(window);
    const aura::Window* transient_parent = wm::GetTransientParent(window);
    if (!snap_group) {
      snap_group =
          snap_group_controller->GetSnapGroupForGivenWindow(transient_parent);
    }

    if (snap_group) {
      const aura::Window* top_most_window_in_snap_group =
          snap_group->GetTopMostWindowInGroup();
      return window != top_most_window_in_snap_group &&
             (!transient_parent ||
              transient_parent != top_most_window_in_snap_group);
    }
  }

  return false;
}

void EnsureTransientRoots(
    std::vector<raw_ptr<aura::Window, VectorExperimental>>* out_window_list) {
  for (auto it = out_window_list->begin(); it != out_window_list->end();) {
    aura::Window* transient_root = ::wm::GetTransientRoot(*it);
    if (*it != transient_root) {
      if (base::Contains(*out_window_list, transient_root)) {
        it = out_window_list->erase(it);
      } else {
        *it = transient_root;
        ++it;
      }
    } else {
      ++it;
    }
  }
}

void MinimizeAndHideWithoutAnimation(
    const std::vector<raw_ptr<aura::Window, VectorExperimental>>& windows) {
  for (aura::Window* window : windows) {
    wm::ScopedAnimationDisabler disable(window);

    // ARC windows are minimized asynchronously, so we hide them after
    // minimization. We minimize ARC windows first so they receive occlusion
    // updates before losing focus from being hidden. See crbug.com/910304.
    // TODO(oshima): Investigate better way to handle ARC apps immediately.

    // Suspect some callsites may use this on a window without a window state
    // (`aura::client::WINDOW_TYPE_CONTROL`) or windows that cannot be
    // minimized. See https://crbug.com/1200596.
    auto* window_state = WindowState::Get(window);
    if (window_state && window_state->CanMinimize())
      window_state->Minimize();

    window->Hide();
  }

  if (windows.size()) {
    // Disabling the animations using `ScopedAnimationDisabler` will skip
    // detaching the resources associated with the layer. So we have to trick
    // the compositor into releasing the resources.
    // crbug.com/924802.
    auto* compositor = windows[0]->layer()->GetCompositor();
    bool was_visible = compositor->IsVisible();
    compositor->SetVisible(false);
    compositor->SetVisible(was_visible);
  }
}

aura::Window* GetRootWindowAt(const gfx::Point& point_in_screen) {
  const display::Display& display =
      display::Screen::GetScreen()->GetDisplayNearestPoint(point_in_screen);
  DCHECK(display.is_valid());
  RootWindowController* root_window_controller =
      Shell::GetRootWindowControllerWithDisplayId(display.id());
  return root_window_controller ? root_window_controller->GetRootWindow()
                                : nullptr;
}

aura::Window* GetRootWindowMatching(const gfx::Rect& rect_in_screen) {
  const display::Display& display =
      display::Screen::GetScreen()->GetDisplayMatching(rect_in_screen);
  RootWindowController* root_window_controller =
      Shell::GetRootWindowControllerWithDisplayId(display.id());
  return root_window_controller ? root_window_controller->GetRootWindow()
                                : nullptr;
}

bool IsArcPipWindow(const aura::Window* window) {
  return IsArcWindow(window) && WindowState::Get(window)->IsPip();
}

void ExpandArcPipWindow() {
  auto* pip_container = Shell::GetContainer(Shell::GetPrimaryRootWindow(),
                                            kShellWindowId_PipContainer);
  if (!pip_container)
    return;

  auto pip_window_iter =
      base::ranges::find_if(pip_container->children(), IsArcPipWindow);
  if (pip_window_iter == pip_container->children().end())
    return;

  auto* window_state = WindowState::Get(*pip_window_iter);
  window_state->Restore();
}

bool IsAnyWindowDragged() {
  OverviewController* overview_controller = Shell::Get()->overview_controller();
  if (overview_controller->InOverviewSession() &&
      overview_controller->overview_session()
          ->GetCurrentDraggedOverviewItem()) {
    return true;
  }

  for (aura::Window* window :
       Shell::Get()->mru_window_tracker()->BuildMruWindowList(kActiveDesk)) {
    if (WindowState::Get(window)->is_dragged())
      return true;
  }
  return false;
}

void FixWindowStackingAccordingToGlobalMru(aura::Window* window_to_fix) {
  aura::Window* container = window_to_fix->parent();
  DCHECK(desks_util::IsDeskContainer(container));
  DCHECK_EQ(window_to_fix, wm::GetTransientRoot(window_to_fix));

  const auto mru_windows =
      Shell::Get()->mru_window_tracker()->BuildWindowListIgnoreModal(
          DesksMruType::kAllDesks);
  // Find the closest sibling that is not a transient descendant, which
  // `window_to_fix` should be stacked below.
  aura::Window* closest_sibling_above_window = nullptr;
  for (aura::Window* window : mru_windows) {
    if (window == window_to_fix) {
      if (closest_sibling_above_window) {
        container->StackChildBelow(window_to_fix, closest_sibling_above_window);
      }
      return;
    }

    if (window->parent() == container &&
        !wm::HasTransientAncestor(window, window_to_fix)) {
      closest_sibling_above_window = window;
    }
  }
}

aura::Window* GetTopWindow() {
  MruWindowTracker::WindowList windows =
      Shell::Get()->mru_window_tracker()->BuildWindowForCycleList(kActiveDesk);

  return windows.empty() ? nullptr : windows[0];
}

aura::Window* GetTopNonFloatedWindow() {
  MruWindowTracker::WindowList windows =
      Shell::Get()->mru_window_tracker()->BuildWindowForCycleList(kActiveDesk);
  for (aura::Window* window : windows) {
    if (!WindowState::Get(window)->IsFloated())
      return window;
  }
  return nullptr;
}

aura::Window* GetFloatedWindowForActiveDesk() {
  return Shell::Get()->float_controller()->FindFloatedWindowOfDesk(
      DesksController::Get()->GetTargetActiveDesk());
}

bool ShouldMinimizeTopWindowOnBack() {
  Shell* shell = Shell::Get();
  // We never want to minimize the main app window in the Kiosk session.
  if (shell->session_controller()->IsRunningInAppMode()) {
    return false;
  }

  if (!display::Screen::GetScreen()->InTabletMode()) {
    return false;
  }

  aura::Window* window = GetTopWindow();
  if (!window) {
    return false;
  }

  // Do not minimize the window if it is in overview. This can avoid unnecessary
  // window minimize animation.
  OverviewController* overview_controller = Shell::Get()->overview_controller();
  if (overview_controller->InOverviewSession() &&
      overview_controller->overview_session()->IsWindowInOverview(window)) {
    return false;
  }

  // ARC and crostini apps will handle the back event that follows on the client
  // side and will minimize/close the window there.
  const chromeos::AppType app_type = window->GetProperty(chromeos::kAppTypeKey);
  if (app_type == chromeos::AppType::ARC_APP ||
      app_type == chromeos::AppType::CROSTINI_APP) {
    return false;
  }

  // Use the value of |kMinimizeOnBackKey| if it is provided. It can be provided
  // by windows with custom web contents.
  bool* can_minimize_on_back_key = window->GetProperty(kMinimizeOnBackKey);
  if (can_minimize_on_back_key)
    return *can_minimize_on_back_key;

  // Minimize the window if it is at the bottom page.
  return !shell->shell_delegate()->CanGoBack(window);
}

bool IsMinimizedOrTucked(aura::Window* window) {
  DCHECK(window->parent());

  WindowState* window_state = WindowState::Get(window);
  if (!window_state) {
    return false;
  }
  if (window_state->IsFloated()) {
    return !window->is_destroying() &&
           Shell::Get()->float_controller()->IsFloatedWindowTuckedForTablet(
               window);
  }
  return window_state->IsMinimized();
}

void SendBackKeyEvent(aura::Window* root_window) {
  // Send up event as well as down event as ARC++ clients expect this
  // sequence.
  // TODO: Investigate if we should be using the current modifiers.
  ui::KeyEvent press_key_event(ui::EventType::kKeyPressed,
                               ui::VKEY_BROWSER_BACK, ui::EF_NONE);
  std::ignore = root_window->GetHost()->SendEventToSink(&press_key_event);
  ui::KeyEvent release_key_event(ui::EventType::kKeyReleased,
                                 ui::VKEY_BROWSER_BACK, ui::EF_NONE);
  std::ignore = root_window->GetHost()->SendEventToSink(&release_key_event);
}

WindowTransientDescendantIteratorRange GetVisibleTransientTreeIterator(
    aura::Window* window) {
  auto hide_predicate = [](aura::Window* window) {
    return window->GetProperty(kHideInOverviewKey);
  };
  return GetTransientTreeIterator(window, base::BindRepeating(hide_predicate));
}

void SetTransform(aura::Window* window, const gfx::Transform& transform) {
  const gfx::PointF target_origin(
      GetUnionScreenBoundsForWindow(window).origin());
  for (auto* window_iter :
       window_util::GetVisibleTransientTreeIterator(window)) {
    if (window_iter->GetProperty(kExcludeFromTransientTreeTransformKey)) {
      continue;
    }
    aura::Window* parent_window = window_iter->parent();
    gfx::RectF original_bounds(window_iter->GetTargetBounds());
    ::wm::TranslateRectToScreen(parent_window, &original_bounds);
    const gfx::Transform new_transform = TransformAboutPivot(
        gfx::PointF(target_origin.x() - original_bounds.x(),
                    target_origin.y() - original_bounds.y()),
        transform);
    window_iter->SetTransform(new_transform);
  }
}

gfx::RectF GetTransformedBounds(aura::Window* transformed_window,
                                int top_inset) {
  gfx::RectF bounds;
  for (auto* window : GetVisibleTransientTreeIterator(transformed_window)) {
    // Ignore other window types when computing bounding box of overview target
    // item.
    if (window != transformed_window &&
        window->GetType() != aura::client::WINDOW_TYPE_NORMAL) {
      continue;
    }
    gfx::RectF window_bounds(window->GetTargetBounds());
    const gfx::Transform new_transform = TransformAboutPivot(
        window_bounds.origin(), window->layer()->GetTargetTransform());
    window_bounds = new_transform.MapRect(window_bounds);

    // The preview title is shown above the preview window. Hide the window
    // header for apps or browser windows with no tabs (web apps) to avoid
    // showing both the window header and the preview title.
    if (top_inset > 0) {
      gfx::RectF header_bounds = window_bounds;
      header_bounds.set_height(top_inset);
      header_bounds = new_transform.MapRect(header_bounds);
      window_bounds.Inset(gfx::InsetsF::TLBR(header_bounds.height(), 0, 0, 0));
    }
    wm::TranslateRectToScreen(window->parent(), &window_bounds);
    bounds.Union(window_bounds);
  }
  return bounds;
}

views::BubbleDialogDelegate* AsBubbleDialogDelegate(
    aura::Window* transient_window) {
  views::Widget* widget =
      views::Widget::GetWidgetForNativeWindow(transient_window);
  if (!widget || !widget->widget_delegate()) {
    return nullptr;
  }
  return widget->widget_delegate()->AsBubbleDialogDelegate();
}

views::DialogDelegate* AsDialogDelegate(aura::Window* transient_window) {
  views::Widget* widget =
      views::Widget::GetWidgetForNativeWindow(transient_window);
  if (!widget || !widget->widget_delegate()) {
    return nullptr;
  }

  return widget->widget_delegate()->AsDialogDelegate();
}

bool ShouldShowForCurrentUser(aura::Window* window) {
  MultiUserWindowManager* multi_user_window_manager =
      MultiUserWindowManagerImpl::Get();
  if (!multi_user_window_manager)
    return true;

  const AccountId account_id =
      multi_user_window_manager->GetUserPresentingWindow(window);
  // An empty account ID is returned if the window is presented for all users.
  if (!account_id.is_valid())
    return true;

  return account_id == multi_user_window_manager->CurrentAccountId();
}

aura::Window* GetEventHandlerForEvent(const ui::LocatedEvent& event) {
  gfx::Point location_in_screen = event.location();
  ::wm::ConvertPointToScreen(static_cast<aura::Window*>(event.target()),
                             &location_in_screen);
  aura::Window* root_window_at_point = GetRootWindowAt(location_in_screen);
  gfx::Point location_in_root = location_in_screen;
  ::wm::ConvertPointFromScreen(root_window_at_point, &location_in_root);
  return root_window_at_point->GetEventHandlerForPoint(location_in_root);
}

bool IsNaturalScrollOn() {
  PrefService* pref =
      Shell::Get()->session_controller()->GetActivePrefService();
  return pref->GetBoolean(prefs::kTouchpadEnabled) &&
         pref->GetBoolean(prefs::kNaturalScroll);
}

bool IsNaturalScrollOn(const ui::ScrollEvent& event) {
  if (auto* touchpad_device_settings =
          InputDeviceSettingsController::Get()->GetTouchpadSettings(
              event.source_device_id())) {
    return touchpad_device_settings->reverse_scrolling;
  }
  // This case mainly happens in the unit tests which generate fake touch
  // events but have no touch devices.
  // TODO(zxdan): We should `CHECK` the setting's ptr after we fix corresponding
  // unit tests.
  return IsNaturalScrollOn();
}

bool ShouldRoundThumbnailWindow(views::View* backdrop_view,
                                const gfx::RectF& thumbnail_bounds_in_screen) {
  // If the backdrop is not created or not visible, round the thumbnail.
  if (!backdrop_view || !backdrop_view->GetVisible()) {
    return true;
  }

  CHECK(backdrop_view->layer());
  // Get the bounds of the backdrop as a rounded rect object. This will allow us
  // to use `gfx::RRectF::Contains` to check if `thumbnail_bounds_in_screen` is
  // inside the rounding. For example, if the x,y,w,h all match and the rounding
  // is non-zero, this will return false as the thumbnails corners will be
  // considered out of bounds.
  const gfx::RRectF backdrop_bounds_in_screen(
      gfx::RRectF(gfx::RectF(backdrop_view->GetBoundsInScreen()),
                  backdrop_view->layer()->rounded_corner_radii()));
  return !backdrop_bounds_in_screen.Contains(thumbnail_bounds_in_screen);
}

float GetSnapRatioForWindow(aura::Window* window) {
  WindowState* window_state = WindowState::Get(window);
  return window_state->snap_ratio().value_or(chromeos::kDefaultSnapRatio);
}

void RegisterProfilePrefs(PrefRegistrySimple* registry) {
  // TODO(sophiewen): Determine whether to enable the setting by default.
  registry->RegisterBooleanPref(prefs::kSnapWindowSuggestions, true);
}

bool IsInFasterSplitScreenSetupSession(const aura::Window* window) {
  SplitViewOverviewSession* split_view_overview_session =
      RootWindowController::ForWindow(window)->split_view_overview_session();
  return !Shell::Get()->IsInTabletMode() && split_view_overview_session &&
         split_view_overview_session->setup_type() ==
             SplitViewOverviewSetupType::kSnapThenAutomaticOverview;
}

bool IsInFasterSplitScreenSetupSession() {
  if (!IsInOverviewSession() || display::Screen::GetScreen()->InTabletMode()) {
    return false;
  }
  auto* overview_session = GetOverviewSession();
  for (const auto& grid : overview_session->grid_list()) {
    // Return true if any grid is in faster splitscreen setup.
    if (IsInFasterSplitScreenSetupSession(grid->root_window())) {
      return true;
    }
  }
  return false;
}

gfx::Rect GetTargetScreenBounds(aura::Window* window) {
  gfx::Rect bounds_in_screen(window->GetTargetBounds());
  wm::ConvertRectToScreen(window->parent(), &bounds_in_screen);
  return bounds_in_screen;
}

}  // namespace ash::window_util