chromium/ash/session/fullscreen_notification_bubble.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/session/fullscreen_notification_bubble.h"

#include <memory>

#include "ash/public/cpp/shell_window_ids.h"
#include "ash/shell.h"
#include "ash/wm/window_state.h"
#include "base/functional/bind.h"
#include "base/location.h"
#include "base/scoped_observation.h"
#include "base/time/time.h"
#include "base/timer/timer.h"
#include "components/fullscreen_control/subtle_notification_view.h"
#include "components/strings/grit/components_strings.h"
#include "ui/aura/window.h"
#include "ui/aura/window_observer.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/gfx/geometry/insets.h"
#include "ui/strings/grit/ui_strings.h"
#include "ui/views/view.h"
#include "ui/views/widget/widget.h"

namespace ash {

namespace {

constexpr base::TimeDelta kAutoHideDelay = base::Seconds(4);
constexpr int kNotificationViewTopDp = 45;

}  // namespace

FullscreenNotificationBubble::FullscreenNotificationBubble()
    : timer_(std::make_unique<base::OneShotTimer>()) {
  // Create the notification view.
  auto subtle_notification_view = std::make_unique<SubtleNotificationView>();
  view_ = subtle_notification_view.get();

  // Set the text displayed in the bubble, including the keyboard key.
  view_->UpdateContent(l10n_util::GetStringFUTF16(
      IDS_FULLSCREEN_PRESS_TO_EXIT_FULLSCREEN,
      l10n_util::GetStringUTF16(IDS_APP_FULLSCREEN_KEY)));

  // The view used to parent the bubble widget
  const gfx::NativeView parent_view =
      Shell::GetPrimaryRootWindow()->GetChildById(
          kShellWindowId_SettingBubbleContainer);

  // Create the widget containing the SubtleNotificationView.
  widget_ = SubtleNotificationView::CreatePopupWidget(
      parent_view, std::move(subtle_notification_view));

  gfx::Rect rect = GetBubbleBounds();
  widget_->SetBounds(rect);
  widget_->SetZOrderLevel(ui::ZOrderLevel::kSecuritySurface);
}

FullscreenNotificationBubble::~FullscreenNotificationBubble() = default;

void FullscreenNotificationBubble::ShowForWindowState(
    WindowState* window_state) {
  // Early out if a WindowState is already tracked. The bubble should already be
  // visible in this case.
  if (window_state_observation_.IsObserving()) {
    // `window_state` arg should match the tracked `window_state_`.
    DCHECK(window_state_observation_.IsObservingSource(window_state));
    DCHECK(window_observation_.IsObservingSource(window_state->window()));
    return;
  }
  window_state_observation_.Observe(window_state);

  // Observe the window to properly handle window destruction.
  DCHECK(!window_observation_.IsObserving());
  window_observation_.Observe(window_state->window());

  Show();
}

void FullscreenNotificationBubble::Show() {
  if (widget_->IsVisible())
    return;

  widget_->Show();

  timer_->Start(FROM_HERE, kAutoHideDelay,
                base::BindOnce(&FullscreenNotificationBubble::Hide,
                               weak_ptr_factory_.GetWeakPtr()));
}

void FullscreenNotificationBubble::Hide() {
  window_observation_.Reset();
  window_state_observation_.Reset();

  if (!widget_->IsVisible())
    return;

  widget_->Hide();
}

void FullscreenNotificationBubble::OnWindowDestroying(aura::Window* window) {
  Hide();
}

void FullscreenNotificationBubble::OnPreWindowStateTypeChange(
    WindowState* window_state,
    chromeos::WindowStateType old_type) {
  bool is_exiting_fullscreen =
      old_type == chromeos::WindowStateType::kFullscreen &&
      !window_state->IsFullscreen();
  if (is_exiting_fullscreen)
    Hide();
}

gfx::Rect FullscreenNotificationBubble::GetBubbleBounds() {
  gfx::Size size(view_->GetPreferredSize());
  gfx::Rect widget_bounds = Shell::GetPrimaryRootWindow()->GetBoundsInScreen();
  int x = widget_bounds.x() + (widget_bounds.width() - size.width()) / 2;

  // |desired_top| is the top of the bubble area including the shadow.
  const int desired_top = kNotificationViewTopDp - view_->GetInsets().top();
  const int y = widget_bounds.y() + desired_top;

  return gfx::Rect(gfx::Point(x, y), size);
}

}  // namespace ash