chromium/ash/components/arc/compat_mode/compat_mode_button_controller.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/components/arc/compat_mode/compat_mode_button_controller.h"

#include "ash/components/arc/compat_mode/arc_resize_lock_pref_delegate.h"
#include "ash/components/arc/compat_mode/arc_window_property_util.h"
#include "ash/components/arc/compat_mode/compat_mode_button.h"
#include "ash/frame/non_client_frame_view_ash.h"
#include "ash/game_dashboard/game_dashboard_controller.h"
#include "ash/public/cpp/app_types_util.h"
#include "ash/public/cpp/arc_compat_mode_util.h"
#include "ash/public/cpp/arc_resize_lock_type.h"
#include "ash/public/cpp/window_properties.h"
#include "ash/resources/vector_icons/vector_icons.h"
#include "base/functional/bind.h"
#include "chromeos/constants/chromeos_features.h"
#include "chromeos/ui/frame/default_frame_header.h"
#include "components/strings/grit/components_strings.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/views/accessibility/view_accessibility.h"
#include "ui/views/vector_icons.h"

namespace arc {

CompatModeButtonController::ButtonState::ButtonState() = default;

CompatModeButtonController::ButtonState::ButtonState(bool enable) {
  this->enable = enable;
}

CompatModeButtonController::ButtonState::ButtonState(
    bool enable,
    const std::u16string& tooltip_text)
    : CompatModeButtonController::ButtonState(enable) {
  this->tooltip_text = tooltip_text;
}

CompatModeButtonController::ButtonState::ButtonState(const ButtonState& other) =
    default;
CompatModeButtonController::ButtonState::~ButtonState() = default;

CompatModeButtonController::CompatModeButtonController() = default;
CompatModeButtonController::~CompatModeButtonController() = default;

void CompatModeButtonController::Update(aura::Window* window) {
  DCHECK(ash::IsArcWindow(window));
  DCHECK(pref_delegate_);

  const auto app_id = GetAppId(window);
  if (!app_id)
    return;
  auto* const frame_header = GetFrameHeader(window);
  // TODO(b/200230343): Replace it with resize lock type.
  const auto resize_lock_state = pref_delegate_->GetResizeLockState(*app_id);
  if (resize_lock_state == mojom::ArcResizeLockState::UNDEFINED ||
      resize_lock_state == mojom::ArcResizeLockState::READY) {
    return;
  }

  // Update the accelerator, for all windows.
  UpdateAshAccelerator(window);

  // Don't show the `CompatModeButton` for game windows.
  if (ash::GameDashboardController::IsGameWindow(window)) {
    return;
  }

  auto* compat_mode_button = frame_header->GetCenterButton();
  if (!compat_mode_button) {
    // The ownership is transferred implicitly with AddChildView in HeaderView,
    // but ideally we want to explicitly manage the lifecycle of this resource.
    compat_mode_button = new CompatModeButton(
        this,
        base::BindRepeating(&CompatModeButtonController::ToggleResizeToggleMenu,
                            GetWeakPtr(), window));
    frame_header->SetCenterButton(compat_mode_button);

    UpdateArrowIcon(window, /*widget_visibility=*/false);

    auto* const frame_view = ash::NonClientFrameViewAsh::Get(window);
    // Ideally, we want HeaderView to update properties, but as currently
    // the center button is set to FrameHeader, we need to call this explicitly.
    // |frame_view| can be null in unittest.
    if (frame_view)
      frame_view->GetHeaderView()->UpdateCaptionButtons();
  }

  const auto mode = ash::compat_mode_util::PredictCurrentMode(window);
  const auto text = ash::compat_mode_util::GetText(mode);

  compat_mode_button->SetImage(views::CAPTION_BUTTON_ICON_CENTER,
                               views::FrameCaptionButton::Animate::kNo,
                               ash::compat_mode_util::GetIcon(mode));
  compat_mode_button->SetText(text);
  compat_mode_button->GetViewAccessibility().SetName(text);

  if (auto button_state = GetButtonState(window)) {
    compat_mode_button->SetEnabled(button_state->enable);
    if (button_state->tooltip_text) {
      compat_mode_button->SetTooltipText(button_state->tooltip_text.value());
    }
  }
}

void CompatModeButtonController::OnButtonPressed() {
  visible_when_button_pressed_ =
      resize_toggle_menu_ && resize_toggle_menu_->IsBubbleShown();
}

void CompatModeButtonController::ClearPrefDelegate() {
  pref_delegate_ = nullptr;
}

void CompatModeButtonController::SetPrefDelegate(
    ArcResizeLockPrefDelegate* pref_delegate) {
  CHECK(!pref_delegate_);
  pref_delegate_ = pref_delegate;
}

std::optional<CompatModeButtonController::ButtonState>
CompatModeButtonController::GetButtonState(const aura::Window* window) const {
  const auto resize_lock_type = window->GetProperty(ash::kArcResizeLockTypeKey);
  switch (resize_lock_type) {
    case ash::ArcResizeLockType::RESIZE_DISABLED_TOGGLABLE:
    case ash::ArcResizeLockType::RESIZE_ENABLED_TOGGLABLE:
      return ButtonState{/*enable=*/true};
    case ash::ArcResizeLockType::RESIZE_DISABLED_NONTOGGLABLE:
      return ButtonState{
          /*enable=*/false,
          l10n_util::GetStringUTF16(
              IDS_ASH_ARC_APP_COMPAT_DISABLED_COMPAT_MODE_BUTTON_TOOLTIP_PHONE)};
    case ash::ArcResizeLockType::NONE:
      // Maximizing an app with RESIZE_ENABLED_TOGGLABLE can lead to this case.
      // Resize lock state shouldn't be updated as the pre-maximized state
      // needs to be restored later.
      return std::nullopt;
  }
}

void CompatModeButtonController::UpdateArrowIcon(aura::Window* window,
                                                 bool widget_visibility) {
  auto* const frame_view = ash::NonClientFrameViewAsh::Get(window);
  // |frame_view| can be null in unittest.
  if (!frame_view) {
    return;
  }

  auto* const compat_mode_button =
      frame_view->GetHeaderView()->GetFrameHeader()->GetCenterButton();
  compat_mode_button->SetSubImage(widget_visibility ? ash::kKsvArrowUpIcon
                                                    : ash::kKsvArrowDownIcon);
  compat_mode_button->SchedulePaint();
}

void CompatModeButtonController::ShowResizeToggleMenu(
    aura::Window* window,
    base::OnceClosure on_bubble_widget_closing_callback) {
  DCHECK(window) << "Invalid window. Unable to display resize toggle menu.";
  DCHECK(ash::IsArcWindow(window))
      << "Cannot display resize toggle menu on a non-ARC window.";

  auto* frame_view = ash::NonClientFrameViewAsh::Get(window);
  DCHECK(frame_view)
      << "Invalid frame view. Unable to display resize toggle menu.";
  resize_toggle_menu_ = std::make_unique<ResizeToggleMenu>(
      std::move(on_bubble_widget_closing_callback), frame_view->frame(),
      pref_delegate_);
}

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

chromeos::FrameHeader* CompatModeButtonController::GetFrameHeader(
    aura::Window* window) {
  auto* const frame_view = ash::NonClientFrameViewAsh::Get(window);
  return frame_view->GetHeaderView()->GetFrameHeader();
}

void CompatModeButtonController::UpdateAshAccelerator(aura::Window* window) {
  auto* const frame_view = ash::NonClientFrameViewAsh::Get(window);
  // |frame_view| can be null in unittest.
  if (!frame_view)
    return;

  const auto resize_lock_type = window->GetProperty(ash::kArcResizeLockTypeKey);
  switch (resize_lock_type) {
    case ash::ArcResizeLockType::RESIZE_DISABLED_TOGGLABLE:
    // TODO(b/200230343): Call NOTREACHED() once the client has shifted to
    // the new protocol.
    case ash::ArcResizeLockType::NONE:
    case ash::ArcResizeLockType::RESIZE_ENABLED_TOGGLABLE:
      frame_view->SetToggleResizeLockMenuCallback(base::BindRepeating(
          &CompatModeButtonController::ToggleResizeToggleMenu, GetWeakPtr(),
          window));
      break;
    case ash::ArcResizeLockType::RESIZE_DISABLED_NONTOGGLABLE:
      frame_view->ClearToggleResizeLockMenuCallback();
      break;
  }
}

void CompatModeButtonController::ToggleResizeToggleMenu(aura::Window* window) {
  if (!window || !ash::IsArcWindow(window)) {
    return;
  }
  DCHECK(pref_delegate_);

  auto* frame_view = ash::NonClientFrameViewAsh::Get(window);
  DCHECK(frame_view);
  const auto* compat_mode_button =
      frame_view->GetHeaderView()->GetFrameHeader()->GetCenterButton();
  if (!compat_mode_button || !compat_mode_button->GetEnabled())
    return;
  if (visible_when_button_pressed_)
    return;
  ShowResizeToggleMenu(
      window, base::BindOnce(&CompatModeButtonController::UpdateArrowIcon,
                             base::Unretained(this), window,
                             /*widget_visibility=*/false));
  UpdateArrowIcon(window, /*widget_visibility=*/true);
}

}  // namespace arc