// Copyright 2023 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/user_education/user_education_help_bubble_controller.h"
#include "ash/session/session_controller_impl.h"
#include "ash/shell.h"
#include "ash/user_education/user_education_delegate.h"
#include "ash/user_education/user_education_types.h"
#include "ash/user_education/user_education_util.h"
#include "ash/user_education/views/help_bubble_factory_views_ash.h"
#include "ash/user_education/views/help_bubble_view_ash.h"
#include "base/cancelable_callback.h"
#include "base/check_is_test.h"
#include "base/check_op.h"
#include "components/account_id/account_id.h"
#include "components/user_education/common/help_bubble.h"
#include "components/user_education/common/help_bubble_params.h"
#include "ui/aura/window.h"
#include "ui/base/interaction/element_identifier.h"
#include "ui/views/interaction/element_tracker_views.h"
#include "ui/views/view.h"
#include "ui/views/widget/widget.h"
namespace ash {
namespace {
// The singleton instance owned by the `UserEducationController`.
UserEducationHelpBubbleController* g_instance = nullptr;
// Helpers ---------------------------------------------------------------------
gfx::Rect GetAnchorBoundsInScreen(const HelpBubbleViewAsh* help_bubble_view) {
return help_bubble_view->GetAnchorView()->GetAnchorBoundsInScreen();
}
aura::Window* GetAnchorRootWindow(const HelpBubbleViewAsh* help_bubble_view) {
return help_bubble_view->GetAnchorView()
->GetWidget()
->GetNativeWindow()
->GetRootWindow();
}
} // namespace
// UserEducationHelpBubbleController -------------------------------------------
UserEducationHelpBubbleController::UserEducationHelpBubbleController(
UserEducationDelegate* delegate)
: delegate_(delegate) {
CHECK_EQ(g_instance, nullptr);
g_instance = this;
}
UserEducationHelpBubbleController::~UserEducationHelpBubbleController() {
CHECK_EQ(g_instance, this);
g_instance = nullptr;
}
// static
UserEducationHelpBubbleController* UserEducationHelpBubbleController::Get() {
// Should only be `nullptr` in testing.
if (!g_instance) {
CHECK_IS_TEST();
}
return g_instance;
}
std::optional<HelpBubbleId> UserEducationHelpBubbleController::GetHelpBubbleId(
ui::ElementIdentifier element_id,
ui::ElementContext element_context) const {
if (help_bubble_ && help_bubble_->IsA<HelpBubbleViewsAsh>()) {
// Cache the `bubble_view` with its associated anchor.
auto* bubble_view = help_bubble_->AsA<HelpBubbleViewsAsh>()->bubble_view();
auto* anchor_view = bubble_view->GetAnchorView();
// Find all `tracked_views` matching `element_id` and `element_context`.
const views::ElementTrackerViews::ViewList tracked_views =
views::ElementTrackerViews::GetInstance()->GetAllMatchingViews(
element_id, element_context);
// A help bubble exists for a `tracked_view` if the `tracked_view` is the
// `anchor_view` for the help bubble.
for (const auto* tracked_view : tracked_views) {
if (tracked_view == anchor_view) {
return bubble_view->id();
}
}
}
return std::nullopt;
}
base::CallbackListSubscription
UserEducationHelpBubbleController::AddHelpBubbleAnchorBoundsChangedCallback(
base::RepeatingClosure callback) {
return help_bubble_anchor_bounds_changed_subscribers_.Add(
std::move(callback));
}
base::CallbackListSubscription
UserEducationHelpBubbleController::AddHelpBubbleClosedCallback(
base::RepeatingClosure callback) {
return help_bubble_closed_subscribers_.Add(std::move(callback));
}
base::CallbackListSubscription
UserEducationHelpBubbleController::AddHelpBubbleShownCallback(
base::RepeatingClosure callback) {
return help_bubble_shown_subscribers_.Add(std::move(callback));
}
void UserEducationHelpBubbleController::NotifyHelpBubbleAnchorBoundsChanged(
base::PassKey<HelpBubbleViewAsh>,
const HelpBubbleViewAsh* help_bubble_view) {
// Ignore event if the associated help bubble has not yet been shown.
if (auto it = help_bubble_metadata_by_key_.find(help_bubble_view);
it != help_bubble_metadata_by_key_.end()) {
it->second.anchor_bounds_in_screen =
GetAnchorBoundsInScreen(help_bubble_view);
help_bubble_anchor_bounds_changed_subscribers_.Notify();
}
}
void UserEducationHelpBubbleController::NotifyHelpBubbleClosed(
base::PassKey<HelpBubbleViewAsh>,
const HelpBubbleViewAsh* help_bubble_view) {
// Ignore event if the associated help bubble has not yet been shown.
if (auto it = help_bubble_metadata_by_key_.find(help_bubble_view);
it != help_bubble_metadata_by_key_.end()) {
help_bubble_metadata_by_key_.erase(it);
help_bubble_closed_subscribers_.Notify();
}
}
void UserEducationHelpBubbleController::NotifyHelpBubbleShown(
base::PassKey<HelpBubbleViewAsh>,
const HelpBubbleViewAsh* help_bubble_view) {
// Ignore event if the associated help bubble has already been shown.
if (!base::Contains(help_bubble_metadata_by_key_, help_bubble_view)) {
help_bubble_metadata_by_key_.emplace(
std::piecewise_construct, std::forward_as_tuple(help_bubble_view),
std::forward_as_tuple(help_bubble_view,
GetAnchorRootWindow(help_bubble_view),
GetAnchorBoundsInScreen(help_bubble_view)));
help_bubble_shown_subscribers_.Notify();
}
}
} // namespace ash