chromium/ash/public/cpp/external_arc/overlay/arc_overlay_controller_impl.cc

// Copyright 2020 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/public/cpp/external_arc/overlay/arc_overlay_controller_impl.h"

#include "ash/wm/window_state.h"
#include "components/exo/shell_surface_util.h"
#include "components/exo/surface.h"
#include "ui/accessibility/ax_enums.mojom.h"
#include "ui/accessibility/ax_node_data.h"
#include "ui/aura/window_targeter.h"
#include "ui/base/metadata/metadata_impl_macros.h"
#include "ui/views/accessibility/view_accessibility.h"
#include "ui/views/widget/widget.h"

namespace ash {
namespace {

class OverlayNativeViewHost final : public views::NativeViewHost {
  METADATA_HEADER(OverlayNativeViewHost, views::NativeViewHost)

 public:
  OverlayNativeViewHost() {
    set_suppress_default_focus_handling();
    GetViewAccessibility().SetRole(ax::mojom::Role::kApplication);
    GetViewAccessibility().SetName(
        std::string(), ax::mojom::NameFrom::kAttributeExplicitlyEmpty);
  }

  OverlayNativeViewHost(const OverlayNativeViewHost&) = delete;
  OverlayNativeViewHost& operator=(const OverlayNativeViewHost&) = delete;
  ~OverlayNativeViewHost() override = default;

  // views::NativeViewHost:
  void OnFocus() override {
    auto* widget = views::Widget::GetWidgetForNativeView(native_view());
    if (widget) {
      GetWidget()->GetFocusManager()->set_shortcut_handling_suspended(true);
      widget->GetNativeWindow()->Focus();
    }
  }
};

BEGIN_METADATA(OverlayNativeViewHost)
END_METADATA

}  // namespace

ArcOverlayControllerImpl::ArcOverlayControllerImpl(aura::Window* host_window)
    : host_window_(host_window) {
  DCHECK(host_window_);

  VLOG(1) << "Host is " << host_window_->GetName();

  host_window_observer_.Observe(host_window_.get());

  overlay_container_ = new OverlayNativeViewHost();
  overlay_container_observer_.Observe(overlay_container_.get());

  auto* const widget = views::Widget::GetWidgetForNativeWindow(
      host_window_->GetToplevelWindow());
  DCHECK(widget);
  DCHECK(widget->GetContentsView());
  widget->GetContentsView()->AddChildView(overlay_container_.get());
}

ArcOverlayControllerImpl::~ArcOverlayControllerImpl() {
  EnsureOverlayWindowClosed();
  OnOverlayWindowClosed();
}

void ArcOverlayControllerImpl::AttachOverlay(aura::Window* overlay_window) {
  if (!overlay_container_ || !host_window_)
    return;

  DCHECK(overlay_window);
  DCHECK(!overlay_container_->native_view())
      << "An overlay is already attached";

  VLOG(1) << "Attaching overlay " << overlay_window->GetName() << " to host "
          << host_window_->GetName();

  overlay_window_ = overlay_window;
  overlay_window_observer_.Observe(overlay_window);

  ash::WindowState* host_window_state =
      ash::WindowState::Get(host_window_->GetToplevelWindow());
  saved_host_can_consume_system_keys_ =
      host_window_state->CanConsumeSystemKeys();
  host_window_state->SetCanConsumeSystemKeys(false);

  overlay_container_->Attach(overlay_window_);
  overlay_container_->GetNativeViewContainer()->SetEventTargeter(
      std::make_unique<aura::WindowTargeter>());

  overlay_container_->SetFocusBehavior(views::View::FocusBehavior::ALWAYS);
  overlay_container_->RequestFocus();

  // Make sure that the overlay comes on top of other windows.
  host_window_->StackChildAtTop(overlay_container_->GetNativeViewContainer());

  UpdateHostBounds();
}

void ArcOverlayControllerImpl::OnWindowDestroying(aura::Window* window) {
  if (host_window_observer_.IsObservingSource(window)) {
    host_window_ = nullptr;
    host_window_observer_.Reset();
    EnsureOverlayWindowClosed();
  }

  if (overlay_window_observer_.IsObservingSource(window))
    OnOverlayWindowClosed();
}

void ArcOverlayControllerImpl::OnViewIsDeleting(views::View* observed_view) {
  if (overlay_container_observer_.IsObservingSource(observed_view)) {
    OnOverlayWindowClosed();
    overlay_container_observer_.Reset();
    overlay_container_ = nullptr;
  }
}

void ArcOverlayControllerImpl::OnWindowBoundsChanged(
    aura::Window* window,
    const gfx::Rect& old_bounds,
    const gfx::Rect& new_bounds,
    ui::PropertyChangeReason reason) {
  if (host_window_observer_.IsObservingSource(window) &&
      old_bounds.size() != new_bounds.size()) {
    UpdateHostBounds();
  }
}

void ArcOverlayControllerImpl::UpdateHostBounds() {
  if (!overlay_container_observer_.IsObserving()) {
    LOG(ERROR) << "No container to resize";
    return;
  }

  gfx::Point origin;
  gfx::Size size = host_window_->bounds().size();
  ConvertPointFromWindow(host_window_, &origin);
  overlay_container_->SetBounds(origin.x(), origin.y(), size.width(),
                                size.height());
}

void ArcOverlayControllerImpl::ConvertPointFromWindow(aura::Window* window,
                                                      gfx::Point* point) {
  views::Widget* const widget = overlay_container_->GetWidget();
  aura::Window::ConvertPointToTarget(window, widget->GetNativeWindow(), point);
  views::View::ConvertPointFromWidget(widget->GetContentsView(), point);
}

void ArcOverlayControllerImpl::EnsureOverlayWindowClosed() {
  // Ensure the overlay window is closed.
  if (overlay_window_observer_.IsObserving()) {
    VLOG(1) << "Forcing-closing overlay " << overlay_window_->GetName();
    auto* const widget =
        views::Widget::GetWidgetForNativeWindow(overlay_window_);
    widget->CloseWithReason(views::Widget::ClosedReason::kUnspecified);
  }
}

void ArcOverlayControllerImpl::OnOverlayWindowClosed() {
  ResetFocusBehavior();
  RestoreHostCanConsumeSystemKeys();
  overlay_window_ = nullptr;
  overlay_window_observer_.Reset();
}

void ArcOverlayControllerImpl::ResetFocusBehavior() {
  if (overlay_container_ && overlay_container_->GetWidget()) {
    overlay_container_->SetFocusBehavior(views::View::FocusBehavior::NEVER);
    overlay_container_->GetWidget()
        ->GetFocusManager()
        ->set_shortcut_handling_suspended(false);
  }
}

void ArcOverlayControllerImpl::RestoreHostCanConsumeSystemKeys() {
  if (host_window_observer_.IsObserving()) {
    ash::WindowState* host_window_state =
        ash::WindowState::Get(host_window_->GetToplevelWindow());
    host_window_state->SetCanConsumeSystemKeys(
        saved_host_can_consume_system_keys_);
  }
}

}  // namespace ash