1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
ash / user_education / user_education_help_bubble_controller.cc [blame]
// 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/help_bubble.h"
#include "components/user_education/common/help_bubble/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