chromium/ash/wm/window_restore/window_restore_util.cc

// Copyright 2021 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_restore/window_restore_util.h"

#include "ash/public/cpp/app_types_util.h"
#include "ash/public/cpp/saved_desk_delegate.h"
#include "ash/public/cpp/window_properties.h"
#include "ash/shell.h"
#include "ash/wm/mru_window_tracker.h"
#include "ash/wm/window_state.h"
#include "base/memory/raw_ptr.h"
#include "base/path_service.h"
#include "base/ranges/algorithm.h"
#include "components/app_constants/constants.h"
#include "components/app_restore/window_properties.h"
#include "ui/aura/client/aura_constants.h"
#include "ui/aura/client/screen_position_client.h"
#include "ui/aura/window.h"
#include "ui/display/display.h"
#include "ui/display/screen.h"
#include "ui/views/widget/widget.h"
#include "ui/wm/core/coordinate_conversion.h"

namespace ash {

namespace {

base::FilePath informed_restore_image_path_for_test_;

// If `use_screen` is true we convert to screen coordinates, otherwise we
// convert to root window coordinates.
gfx::Rect GetBoundsIgnoringTransforms(const aura::Window* window,
                                      bool use_screen) {
  // `aura::Window::Get*Bounds*` is affected by transforms, which may be the
  // case when in overview mode. Compute the bounds in screen minus the
  // transform.
  auto* client = aura::client::GetScreenPositionClient(window->GetRootWindow());
  DCHECK(client);
  gfx::Point origin;
  if (use_screen) {
    client->ConvertPointToScreenIgnoringTransforms(window, &origin);
  } else {
    client->ConvertPointToRootWindowIgnoringTransforms(window, &origin);
  }
  return gfx::Rect(origin, window->bounds().size());
}

}  // namespace

std::unique_ptr<app_restore::WindowInfo> BuildWindowInfo(
    aura::Window* window,
    std::optional<int> activation_index,
    const std::vector<raw_ptr<aura::Window, VectorExperimental>>& mru_windows) {
  auto window_info = std::make_unique<app_restore::WindowInfo>();
  int window_activation_index = -1;
  if (activation_index) {
    window_activation_index = *activation_index;
  } else {
    auto it = base::ranges::find(mru_windows, window);
    if (it != mru_windows.end())
      window_activation_index = it - mru_windows.begin();
  }
  if (window_activation_index != -1)
    window_info->activation_index = window_activation_index;
  window_info->window = window;

  // Set either the `desk_id` or set the `desk_guid`, but not both.
  const int desk_id = window->GetProperty(aura::client::kWindowWorkspaceKey);
  if (desk_id == aura::client::kWindowWorkspaceVisibleOnAllWorkspaces) {
    window_info->desk_id = desk_id;
  } else {
    const std::string* desk_uuid =
        window->GetProperty(aura::client::kDeskUuidKey);
    // It's possible for the desk to no longer exist or not be found in the case
    // of CloseAll.
    window_info->desk_guid =
        desk_uuid ? base::Uuid::ParseLowercase(*desk_uuid) : base::Uuid();
  }

  // If override bounds and window state are available (in tablet mode), save
  // those bounds.
  gfx::Rect* override_bounds = window->GetProperty(kRestoreBoundsOverrideKey);
  WindowState* window_state = WindowState::Get(window);
  if (override_bounds) {
    window_info->current_bounds = *override_bounds;
    // Snapped and floated states can be restored from tablet onto clamshell, so
    // we do not use the restore override state here.
    window_info->window_state_type =
        window_state->IsSnapped() || window_state->IsFloated()
            ? window_state->GetStateType()
            : window->GetProperty(kRestoreWindowStateTypeOverrideKey);
  } else {
    // If there are restore bounds, use those as current bounds. On restore, for
    // states with restore bounds (maximized, minimized, snapped, etc), they
    // will take the current bounds as their restore bounds and have the current
    // bounds determined by the system.
    // Note that for floated state, the window should be restored to its current
    // floated bounds since it's not stored in restore bounds.
    if (window_state->HasRestoreBounds() && !window_state->IsFloated()) {
      window_info->current_bounds = window_state->GetRestoreBoundsInScreen();
    } else {
      window_info->current_bounds =
          GetBoundsIgnoringTransforms(window, /*use_screen=*/true);
    }

    // Window restore does not support restoring fullscreen windows. If a window
    // is fullscreen save the pre-fullscreen window state instead.
    window_info->window_state_type =
        window_state->IsFullscreen()
            ? chromeos::ToWindowStateType(
                  window->GetProperty(aura::client::kRestoreShowStateKey))
            : window_state->GetStateType();
  }

  // Populate the restore show state field that the minimize should restore back
  // to if the window is minimized.
  if (window_state->IsMinimized()) {
    window_info->pre_minimized_show_state_type =
        window->GetProperty(aura::client::kRestoreShowStateKey);
  }

  if (window_state->IsSnapped()) {
    // `WindowState::snap_ratio_` is stored as a float between 0 and 1. Convert
    // it to a percentage here.
    std::optional<float> snap_ratio = window_state->snap_ratio();
    window_info->snap_percentage =
        snap_ratio.has_value() ? std::make_optional(std::round(
                                     100 * window_state->snap_ratio().value()))
                               : std::nullopt;
  }

  window_info->display_id =
      display::Screen::GetScreen()->GetDisplayNearestWindow(window).id();

  // For saved desks, store the readable app name so that we can have a nice
  // error message if the user tries to used the saved desk on a device that
  // doesn't have the app.
  std::string* app_id = window->GetProperty(kAppIDKey);
  window_info->app_title =
      app_id
          ? base::UTF8ToUTF16(
                Shell::Get()->saved_desk_delegate()->GetAppShortName(*app_id))
          : window->GetTitle();

  // Save window size restriction of ARC app window.
  if (IsArcWindow(window)) {
    views::Widget* widget = views::Widget::GetWidgetForNativeWindow(window);
    if (widget) {
      window_info->arc_extra_info = {
          .maximum_size = widget->GetMaximumSize(),
          .minimum_size = widget->GetMinimumSize(),
          .bounds_in_root =
              GetBoundsIgnoringTransforms(window, /*use_screen=*/false)};
      window_info->app_title = window->GetTitle();
    }
  }

  return window_info;
}

bool IsBrowserAppId(const std::string& id) {
  return id == app_constants::kChromeAppId || id == app_constants::kLacrosAppId;
}

base::FilePath GetInformedRestoreImagePath() {
  if (!informed_restore_image_path_for_test_.empty()) {
    return informed_restore_image_path_for_test_;
  }
  base::FilePath home_dir;
  CHECK(base::PathService::Get(base::DIR_HOME, &home_dir));
  return home_dir.AppendASCII("informed_restore_image.png");
}

void SetInformedRestoreImagePathForTest(const base::FilePath& path) {
  informed_restore_image_path_for_test_ = path;
}

}  // namespace ash