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
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
ash / birch / birch_coral_item.cc [blame]
// Copyright 2024 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/birch/birch_coral_item.h"
#include "ash/birch/birch_coral_grouped_icon_image.h"
#include "ash/birch/birch_coral_provider.h"
#include "ash/birch/birch_model.h"
#include "ash/birch/coral_util.h"
#include "ash/constants/notifier_catalogs.h"
#include "ash/public/cpp/coral_delegate.h"
#include "ash/public/cpp/saved_desk_delegate.h"
#include "ash/shell.h"
#include "ash/strings/grit/ash_strings.h"
#include "ash/system/toast/toast_manager_impl.h"
#include "ash/wm/desks/desk.h"
#include "ash/wm/desks/desks_controller.h"
#include "ash/wm/desks/overview_desk_bar_view.h"
#include "ash/wm/overview/birch/birch_chip_button_base.h"
#include "ash/wm/overview/overview_controller.h"
#include "ash/wm/overview/overview_grid.h"
#include "ash/wm/overview/overview_session.h"
#include "base/barrier_callback.h"
#include "base/metrics/histogram_functions.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/gfx/image/image_skia.h"
#include "ui/gfx/image/image_skia_operations.h"
namespace ash {
namespace {
constexpr int kCoralIconSize = 14;
constexpr int kCoralAppIconDesiredSize = 64;
constexpr int kCoralMaxSubIconsNum = 4;
constexpr char kMaxDesksToastId[] = "coral_max_desks_toast";
// Callback for the favicon load request in `GetFaviconImageCoral()`. If the
// load fails, passes an empty `ui::ImageModel` to the `barrier_callback`.
void OnGotFaviconImageCoral(
base::OnceCallback<void(const ui::ImageModel&)> barrier_callback,
const ui::ImageModel& image) {
BirchClient* client = Shell::Get()->birch_model()->birch_client();
if (image.IsImage()) {
std::move(barrier_callback).Run(std::move(image));
} else {
// We need to use this method because the `ui::ImageModel` is constructed
// from a `gfx::ImageSkia` and not a vector icon.
std::move(barrier_callback).Run(client->GetChromeBackupIcon());
}
}
// Callback for the app icon load request in `GetAppIconCoral()`. If the
// load fails, passes an empty `ui::ImageModel` to the `barrier_callback`.
void OnGotAppIconCoral(
base::OnceCallback<void(const ui::ImageModel&)> barrier_callback,
const gfx::ImageSkia& image) {
if (!image.isNull()) {
std::move(barrier_callback)
.Run(std::move(ui::ImageModel::FromImageSkia(image)));
} else {
// TODO(zxdan): Define a backup icon for apps.
std::move(barrier_callback).Run(ui::ImageModel());
}
}
// Draws the Coral grouped icon image with the loaded icons, and passes the
// final result to `BirchChipButton`.
void OnAllFaviconsRetrievedCoral(
base::OnceCallback<void(const ui::ImageModel&)> final_callback,
int extra_number,
const std::vector<ui::ImageModel>& loaded_icons) {
std::vector<gfx::ImageSkia> resized_icons;
for (const auto& loaded_icon : loaded_icons) {
// TODO(zxdan): Once all favicons have backup icons, change this to CHECK.
if (!loaded_icon.IsEmpty()) {
// Only a `ui::ImageModel` constructed from a `gfx::ImageSkia` produces a
// valid result from `GetImage()`. Vector icons will not work.
resized_icons.emplace_back(gfx::ImageSkiaOperations::CreateResizedImage(
loaded_icon.GetImage().AsImageSkia(),
skia::ImageOperations::RESIZE_BEST,
gfx::Size(kCoralIconSize, kCoralIconSize)));
}
}
ui::ImageModel composed_image =
CoralGroupedIconImage::DrawCoralGroupedIconImage(
/*icons_images=*/resized_icons, extra_number);
std::move(final_callback).Run(std::move(composed_image));
}
} // namespace
BirchCoralItem::BirchCoralItem(const std::u16string& coral_title,
const std::u16string& coral_text,
CoralSource source,
const base::Token& group_id)
: BirchItem(coral_title, coral_text),
source_(source),
group_id_(group_id) {}
BirchCoralItem::BirchCoralItem(BirchCoralItem&&) = default;
BirchCoralItem::BirchCoralItem(const BirchCoralItem&) = default;
BirchCoralItem& BirchCoralItem::operator=(const BirchCoralItem&) = default;
bool BirchCoralItem::operator==(const BirchCoralItem& rhs) const = default;
BirchCoralItem::~BirchCoralItem() = default;
void BirchCoralItem::LaunchGroup(BirchChipButtonBase* birch_chip_button) {
// Record basic metrics.
RecordActionMetrics();
auto* birch_coral_provider = BirchCoralProvider::Get();
switch (source_) {
case CoralSource::kPostLogin: {
coral::mojom::GroupPtr group =
birch_coral_provider->ExtractGroupById(group_id_);
Shell::Get()->coral_delegate()->LaunchPostLoginGroup(std::move(group));
BirchCoralProvider::Get()->OnPostLoginClusterRestored();
base::UmaHistogramEnumeration("Ash.Birch.Coral.Action",
ActionType::kRestore);
// End the Overview after restore.
// TODO(zxdan|sammie): Consider the restoring failed cases.
OverviewController::Get()->EndOverview(OverviewEndAction::kCoral,
OverviewEnterExitType::kNormal);
break;
}
case CoralSource::kInSession: {
if (!DesksController::Get()->CanCreateDesks()) {
ToastData toast(
kMaxDesksToastId, ToastCatalogName::kVirtualDesksLimitMax,
l10n_util::GetStringUTF16(IDS_ASH_DESKS_MAX_NUM_REACHED),
ToastData::kDefaultToastDuration,
/*visible_on_lock_screen=*/false);
Shell::Get()->toast_manager()->Show(std::move(toast));
return;
}
// Note that we should cache the active root window before launching the
// group. Otherwise, `birch_chip_button` will be removed.
aura::Window* active_root =
birch_chip_button->GetWidget()->GetNativeWindow()->GetRootWindow();
// Cache the in-session source desk before extracting the group in case
// the pointer gets reset.
const Desk* source_desk = birch_coral_provider->in_session_source_desk();
CHECK(source_desk);
coral::mojom::GroupPtr group =
birch_coral_provider->ExtractGroupById(group_id_);
Shell::Get()->coral_controller()->OpenNewDeskWithGroup(std::move(group),
source_desk);
base::UmaHistogramEnumeration("Ash.Birch.Coral.Action",
ActionType::kLaunchToNewDesk);
// Nudge the desk name on the same display with the `birch_chip_button`.
auto* overview_controller = Shell::Get()->overview_controller();
CHECK(overview_controller->InOverviewSession());
overview_controller->overview_session()
->GetGridWithRootWindow(active_root)
->desks_bar_view()
->NudgeDeskName(DesksController::Get()->GetActiveDeskIndex());
break;
}
case CoralSource::kUnknown:
NOTREACHED() << "Invalid response with unknown source.";
}
}
BirchItemType BirchCoralItem::GetType() const {
return BirchItemType::kCoral;
}
std::string BirchCoralItem::ToString() const {
auto root = base::Value::Dict().Set(
"Coral item",
base::Value::Dict().Set("Title", title()).Set("Subtitle", subtitle()));
return root.DebugString();
}
base::Value::Dict BirchCoralItem::ToCoralItemDetails() const {
const coral::mojom::GroupPtr& group =
BirchCoralProvider::Get()->GetGroupById(group_id_);
base::Value::List items;
for (const auto& entity : group->entities) {
if (entity->is_tab()) {
items.Append(base::Value::Dict()
.Set("type", "tab")
.Set("title", entity->get_tab()->title)
.Set("url", entity->get_tab()->url.spec()));
} else {
items.Append(base::Value::Dict()
.Set("type", "app")
.Set("title", entity->get_app()->title)
.Set("id", entity->get_app()->id));
}
}
return std::move(
base::Value::Dict().Set("title", title()).Set("items", std::move(items)));
}
void BirchCoralItem::PerformAction() {}
// TODO(b/362530155): Consider refactoring icon loading logic into
// `CoralGroupedIconImage`.
void BirchCoralItem::LoadIcon(LoadIconCallback original_callback) const {
const coral::mojom::GroupPtr& group =
BirchCoralProvider::Get()->GetGroupById(group_id_);
const coral_util::TabsAndApps tabs_apps =
coral_util::SplitContentData(group->entities);
const int page_num = tabs_apps.tabs.size();
const int app_num = tabs_apps.apps.size();
const int total_count = page_num + app_num;
// If the total number of pages and apps exceeds the limit of number of sub
// icons, only show 3 icons and one extra number label. Otherwise, show all
// the icons.
const int icon_requests =
total_count > kCoralMaxSubIconsNum ? 3 : total_count;
// Barrier callback that collects the results of multiple favicon loads and
// runs the original load icon callback.
const auto barrier_callback = base::BarrierCallback<const ui::ImageModel&>(
/*num_callbacks=*/icon_requests,
/*done_callback=*/base::BindOnce(
OnAllFaviconsRetrievedCoral,
base::BindOnce(std::move(original_callback),
PrimaryIconType::kCoralGroupIcon,
SecondaryIconType::kNoIcon),
/*extra_number=*/total_count > icon_requests
? total_count - icon_requests
: 0));
for (int i = 0; i < std::min(icon_requests, page_num); i++) {
// For each `url`, retrieve the icon using favicon service, and run the
// `barrier_callback` with the image result.
GetFaviconImageCoral(tabs_apps.tabs[i].url, barrier_callback);
}
for (int i = 0; i < icon_requests - page_num; i++) {
// For each `id`, retrieve the icon using `saved_desk_delegate`, and run the
// `barrier_callback` with the image result.
GetAppIconCoral(tabs_apps.apps[i].id, barrier_callback);
}
}
BirchAddonType BirchCoralItem::GetAddonType() const {
return BirchAddonType::kCoralButton;
}
std::u16string BirchCoralItem::GetAddonAccessibleName() const {
// The add on tooltip and a11y name is determined by the presence of the
// selection UI. It will be handled in `BirchChipButton`.
return u"Placeholder";
}
void BirchCoralItem::GetFaviconImageCoral(
const GURL& url,
base::OnceCallback<void(const ui::ImageModel&)> barrier_callback) const {
BirchClient* client = Shell::Get()->birch_model()->birch_client();
client->GetFaviconImage(
url, /*is_page_url=*/true,
base::BindOnce(OnGotFaviconImageCoral, std::move(barrier_callback)));
}
void BirchCoralItem::GetAppIconCoral(
const std::string& app_id,
base::OnceCallback<void(const ui::ImageModel&)> barrier_callback) const {
Shell::Get()->saved_desk_delegate()->GetIconForAppId(
app_id, kCoralAppIconDesiredSize,
base::BindOnce(OnGotAppIconCoral, std::move(barrier_callback)));
}
} // namespace ash