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
ash / wm / desks / templates / restore_data_collector.cc [blame]
// Copyright 2022 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/wm/desks/templates/restore_data_collector.h"
#include "ash/multi_user/multi_user_window_manager_impl.h"
#include "ash/public/cpp/desk_profiles_delegate.h"
#include "ash/public/cpp/multi_user_window_manager.h"
#include "ash/public/cpp/saved_desk_delegate.h"
#include "ash/public/cpp/window_properties.h"
#include "ash/shell.h"
#include "ash/wm/desks/desk.h"
#include "ash/wm/desks/desks_controller.h"
#include "ash/wm/desks/templates/saved_desk_dialog_controller.h"
#include "ash/wm/desks/templates/saved_desk_util.h"
#include "ash/wm/mru_window_tracker.h"
#include "ash/wm/overview/overview_controller.h"
#include "ash/wm/window_restore/window_restore_util.h"
#include "base/uuid.h"
#include "chromeos/constants/chromeos_features.h"
#include "components/app_restore/app_launch_info.h"
#include "components/app_restore/restore_data.h"
#include "components/app_restore/window_info.h"
#include "ui/wm/core/window_util.h"
namespace ash {
RestoreDataCollector::Call::Call()
: data(std::make_unique<app_restore::RestoreData>()) {}
RestoreDataCollector::Call::Call(RestoreDataCollector::Call&&) = default;
RestoreDataCollector::Call& RestoreDataCollector::Call::operator=(Call&&) =
default;
RestoreDataCollector::Call::~Call() = default;
RestoreDataCollector::RestoreDataCollector() = default;
RestoreDataCollector::~RestoreDataCollector() = default;
void RestoreDataCollector::CaptureActiveDeskAsSavedDesk(
GetDeskTemplateCallback callback,
DeskTemplateType template_type,
const std::string& template_name,
aura::Window* root_window_to_show,
AccountId current_account_id,
const base::flat_set<std::string>& coral_app_id_allowlist) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
const auto current_serial = serial_++;
auto emplace_result = calls_.emplace(current_serial, Call{});
DCHECK(emplace_result.second);
Call& call = emplace_result.first->second;
call.root_window_to_show = root_window_to_show;
call.template_type = template_type;
call.template_name = template_name;
// Lacros profile IDs cannot be transferred between devices and is therefore
// only enabled for save & recall (which is not synced between devices).
if (template_type == DeskTemplateType::kSaveAndRecall &&
chromeos::features::IsDeskProfilesEnabled()) {
call.lacros_profile_id =
DesksController::Get()->active_desk()->lacros_profile_id();
}
auto* window_manager = MultiUserWindowManagerImpl::Get();
auto* const shell = Shell::Get();
auto mru_windows =
shell->mru_window_tracker()->BuildMruWindowList(kActiveDesk);
auto* delegate = shell->saved_desk_delegate();
bool has_supported_apps = false;
for (aura::Window* window : mru_windows) {
// Skip transient windows without reporting.
if (wm::GetTransientParent(window)) {
continue;
}
const std::string app_id = saved_desk_util::GetAppId(window);
// Coral desk templates only contain a subset of the apps on the active
// desk.
if (template_type == DeskTemplateType::kCoral &&
!coral_app_id_allowlist.contains(app_id)) {
continue;
}
if (template_type == DeskTemplateType::kFloatingWorkspace) {
// Filter the windows by profile ID associated with each window. Only save
// the windows that are attached to the primary profile ID. For lacros,
// the window profile id is non-zero. We can skip this check if it's on
// ash.
auto* desk_profile_delegate = Shell::Get()->GetDeskProfilesDelegate();
CHECK(desk_profile_delegate);
const uint64_t primary_profile_id =
desk_profile_delegate->GetPrimaryProfileId();
if (window->GetProperty(ash::kLacrosProfileId) != 0 &&
window->GetProperty(ash::kLacrosProfileId) != primary_profile_id) {
continue;
}
}
// If `window_manager` is not nullptr, then we have a multi profile
// session. We need to make sure that the windows we are capturing belongs
// to the owner of the current active session.
if (window_manager) {
// Skip windows that belong to another profile user.
const AccountId& window_owner = window_manager->GetWindowOwner(window);
if (window_owner.is_valid() && current_account_id != window_owner) {
continue;
}
}
if (!delegate->IsWindowSupportedForSavedDesk(window)) {
call.unsupported_apps.push_back(window);
if (!delegate->IsWindowPersistable(window)) {
++call.non_persistable_window_count;
}
continue;
}
// Skip windows that do not associate with a full restore app id.
if (!Shell::Get()
->overview_controller()
->disable_app_id_check_for_saved_desks() &&
app_id.empty()) {
call.unsupported_apps.push_back(window);
continue;
}
has_supported_apps = true;
std::unique_ptr<app_restore::WindowInfo> window_info =
BuildWindowInfo(window, /*activation_index=*/std::nullopt, mru_windows);
// Clear the desk ID and uuid in the WindowInfo that is to be stored in
// the template. They will be set to the newly created desk when
// launching.
window_info->desk_id.reset();
window_info->desk_guid = base::Uuid();
++call.pending_request_count;
delegate->GetAppLaunchDataForSavedDesk(
window, base::BindOnce(&RestoreDataCollector::OnAppLaunchDataReceived,
base::Unretained(this), current_serial, app_id,
std::move(window_info)));
}
// Do not create a saved desk if the desk is empty or only contains
// unsupported apps.
if (!has_supported_apps) {
calls_.erase(current_serial);
std::move(callback).Run(nullptr);
return;
}
if (root_window_to_show) {
window_tracker_.Add(root_window_to_show);
}
call.callback = std::move(callback);
// If all requests in the loop above returned data synchronously, then we
// have no pending requests and send the data right away. Otherwise it will be
// sent after the last pending request is handled.
if (call.pending_request_count == 0) {
SendDeskTemplate(current_serial);
}
}
void RestoreDataCollector::OnAppLaunchDataReceived(
uint32_t serial,
const std::string& app_id,
std::unique_ptr<app_restore::WindowInfo> window_info,
std::unique_ptr<app_restore::AppLaunchInfo> app_launch_info) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
auto call_it = calls_.find(serial);
DCHECK(call_it != calls_.end());
Call& call = call_it->second;
DCHECK(call.data);
DCHECK_GT(call.pending_request_count, 0u);
--call.pending_request_count;
// nullptr means that this app does not have any data to save.
if (app_launch_info) {
const int32_t window_id = *app_launch_info->window_id;
call.data->AddAppLaunchInfo(std::move(app_launch_info));
call.data->ModifyWindowInfo(app_id, window_id, *window_info);
}
// Null callback here means that the loop in
// `CaptureActiveDeskAsSavedDesk()` has not yet finished polling the
// windows. Non-zero pending request count means that some of preceding
// requests were asynchronous.
if (call.pending_request_count == 0 && !call.callback.is_null()) {
SendDeskTemplate(serial);
}
}
void RestoreDataCollector::SendDeskTemplate(uint32_t serial) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
auto call_it = calls_.find(serial);
DCHECK(call_it != calls_.end());
Call& call = call_it->second;
auto desk_template = std::make_unique<DeskTemplate>(
base::Uuid::GenerateRandomV4(), DeskTemplateSource::kUser,
call.template_name, base::Time::Now(), call.template_type);
desk_template->set_desk_restore_data(std::move(call.data));
if (call.lacros_profile_id) {
desk_template->set_lacros_profile_id(call.lacros_profile_id);
}
if (!call.unsupported_apps.empty() &&
Shell::Get()->overview_controller()->InOverviewSession()) {
// The ideal root window may have gone by now. In that case fall back to
// the primary root one.
auto* root_window_to_show = call.root_window_to_show.get();
if (root_window_to_show && window_tracker_.Contains(root_window_to_show)) {
window_tracker_.Remove(root_window_to_show);
} else {
root_window_to_show = Shell::Get()->GetPrimaryRootWindow();
}
// There were some unsupported apps in the active desk so open up a dialog
// to let the user know. The dialog controller should always be available
// here since we have already determined that we are in overview mode.
auto* dialog_controller = saved_desk_util::GetSavedDeskDialogController();
DCHECK(dialog_controller);
dialog_controller->ShowUnsupportedAppsDialog(
root_window_to_show, call.unsupported_apps,
call.non_persistable_window_count, std::move(call.callback),
std::move(desk_template));
} else {
std::move(call.callback).Run(std::move(desk_template));
}
calls_.erase(call_it);
}
} // namespace ash