1 // Copyright 2019 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "ash/wm/desks/desks_controller.h"
6
7 #include <utility>
8
9 #include "ash/accessibility/accessibility_controller_impl.h"
10 #include "ash/public/cpp/ash_features.h"
11 #include "ash/public/cpp/shelf_model.h"
12 #include "ash/public/cpp/shelf_types.h"
13 #include "ash/public/cpp/shell_window_ids.h"
14 #include "ash/public/cpp/window_properties.h"
15 #include "ash/session/session_controller_impl.h"
16 #include "ash/shell.h"
17 #include "ash/strings/grit/ash_strings.h"
18 #include "ash/wm/desks/desk.h"
19 #include "ash/wm/desks/desk_animation_base.h"
20 #include "ash/wm/desks/desk_animation_impl.h"
21 #include "ash/wm/desks/desks_animations.h"
22 #include "ash/wm/desks/desks_restore_util.h"
23 #include "ash/wm/desks/desks_util.h"
24 #include "ash/wm/mru_window_tracker.h"
25 #include "ash/wm/overview/overview_controller.h"
26 #include "ash/wm/overview/overview_grid.h"
27 #include "ash/wm/overview/overview_item.h"
28 #include "ash/wm/splitview/split_view_controller.h"
29 #include "ash/wm/splitview/split_view_utils.h"
30 #include "ash/wm/switchable_windows.h"
31 #include "ash/wm/window_cycle_controller.h"
32 #include "ash/wm/window_util.h"
33 #include "base/auto_reset.h"
34 #include "base/check_op.h"
35 #include "base/containers/unique_ptr_adapters.h"
36 #include "base/metrics/histogram_functions.h"
37 #include "base/metrics/histogram_macros.h"
38 #include "base/notreached.h"
39 #include "base/numerics/ranges.h"
40 #include "base/stl_util.h"
41 #include "base/timer/timer.h"
42 #include "ui/base/l10n/l10n_util.h"
43 #include "ui/wm/public/activation_client.h"
44
45 namespace ash {
46
47 namespace {
48
49 constexpr char kNewDeskHistogramName[] = "Ash.Desks.NewDesk2";
50 constexpr char kDesksCountHistogramName[] = "Ash.Desks.DesksCount2";
51 constexpr char kRemoveDeskHistogramName[] = "Ash.Desks.RemoveDesk";
52 constexpr char kDeskSwitchHistogramName[] = "Ash.Desks.DesksSwitch";
53 constexpr char kMoveWindowFromActiveDeskHistogramName[] =
54 "Ash.Desks.MoveWindowFromActiveDesk";
55 constexpr char kNumberOfWindowsOnDesk_1_HistogramName[] =
56 "Ash.Desks.NumberOfWindowsOnDesk_1";
57 constexpr char kNumberOfWindowsOnDesk_2_HistogramName[] =
58 "Ash.Desks.NumberOfWindowsOnDesk_2";
59 constexpr char kNumberOfWindowsOnDesk_3_HistogramName[] =
60 "Ash.Desks.NumberOfWindowsOnDesk_3";
61 constexpr char kNumberOfWindowsOnDesk_4_HistogramName[] =
62 "Ash.Desks.NumberOfWindowsOnDesk_4";
63
64 constexpr char kNumberOfDeskTraversalsHistogramName[] =
65 "Ash.Desks.NumberOfDeskTraversals";
66 constexpr int kDeskTraversalsMaxValue = 20;
67
68 // After an desk activation animation starts,
69 // |kNumberOfDeskTraversalsHistogramName| will be recorded after this time
70 // interval.
71 constexpr base::TimeDelta kDeskTraversalsTimeout =
72 base::TimeDelta::FromSeconds(5);
73
74 // Appends the given |windows| to the end of the currently active overview mode
75 // session such that the most-recently used window is added first. If
76 // The windows will animate to their positions in the overview grid.
AppendWindowsToOverview(const std::vector<aura::Window * > & windows)77 void AppendWindowsToOverview(const std::vector<aura::Window*>& windows) {
78 DCHECK(Shell::Get()->overview_controller()->InOverviewSession());
79
80 auto* overview_session =
81 Shell::Get()->overview_controller()->overview_session();
82 for (auto* window :
83 Shell::Get()->mru_window_tracker()->BuildMruWindowList(kActiveDesk)) {
84 if (!base::Contains(windows, window) ||
85 window_util::ShouldExcludeForOverview(window)) {
86 continue;
87 }
88
89 overview_session->AppendItem(window, /*reposition=*/true, /*animate=*/true);
90 }
91 }
92
93 // Removes all the items that currently exist in overview.
RemoveAllWindowsFromOverview()94 void RemoveAllWindowsFromOverview() {
95 DCHECK(Shell::Get()->overview_controller()->InOverviewSession());
96
97 auto* overview_session =
98 Shell::Get()->overview_controller()->overview_session();
99 for (const auto& grid : overview_session->grid_list()) {
100 while (!grid->empty())
101 overview_session->RemoveItem(grid->window_list()[0].get());
102 }
103 }
104
GetDeskDefaultName(size_t desk_index)105 base::string16 GetDeskDefaultName(size_t desk_index) {
106 DCHECK_LT(desk_index, desks_util::kMaxNumberOfDesks);
107 constexpr int kStringIds[] = {IDS_ASH_DESKS_DESK_1_MINI_VIEW_TITLE,
108 IDS_ASH_DESKS_DESK_2_MINI_VIEW_TITLE,
109 IDS_ASH_DESKS_DESK_3_MINI_VIEW_TITLE,
110 IDS_ASH_DESKS_DESK_4_MINI_VIEW_TITLE};
111 static_assert(desks_util::kMaxNumberOfDesks == base::size(kStringIds),
112 "Wrong default desks' names.");
113
114 return l10n_util::GetStringUTF16(kStringIds[desk_index]);
115 }
116
117 // Updates the |ShelfItem::is_on_active_desk| of the items associated with
118 // |windows_on_inactive_desk| and |windows_on_active_desk|. The items of the
119 // given windows will be updated, while the rest will remain unchanged. Either
120 // or both window lists can be empty.
MaybeUpdateShelfItems(const std::vector<aura::Window * > & windows_on_inactive_desk,const std::vector<aura::Window * > & windows_on_active_desk)121 void MaybeUpdateShelfItems(
122 const std::vector<aura::Window*>& windows_on_inactive_desk,
123 const std::vector<aura::Window*>& windows_on_active_desk) {
124 if (!features::IsPerDeskShelfEnabled())
125 return;
126
127 auto* shelf_model = ShelfModel::Get();
128 DCHECK(shelf_model);
129 std::vector<ShelfModel::ItemDeskUpdate> shelf_items_updates;
130
131 auto add_shelf_item_update = [&](aura::Window* window,
132 bool is_on_active_desk) {
133 const ShelfID shelf_id =
134 ShelfID::Deserialize(window->GetProperty(kShelfIDKey));
135 const int index = shelf_model->ItemIndexByID(shelf_id);
136
137 if (index < 0)
138 return;
139
140 shelf_items_updates.push_back({index, is_on_active_desk});
141 };
142
143 for (auto* window : windows_on_inactive_desk)
144 add_shelf_item_update(window, /*is_on_active_desk=*/false);
145 for (auto* window : windows_on_active_desk)
146 add_shelf_item_update(window, /*is_on_active_desk=*/true);
147
148 shelf_model->UpdateItemsForDeskChange(shelf_items_updates);
149 }
150
IsParentSwitchableContainer(const aura::Window * window)151 bool IsParentSwitchableContainer(const aura::Window* window) {
152 DCHECK(window);
153 return window->parent() && IsSwitchableContainer(window->parent());
154 }
155
156 } // namespace
157
158 // Helper class which wraps around a OneShotTimer and used for recording how
159 // many times the user has traversed desks. Here traversal means the amount of
160 // times the user has seen a visual desk change. This differs from desk
161 // activation as a desk is only activated as needed for a screenshot during an
162 // animation. The user may bounce back and forth on two desks that already
163 // have screenshots, and each bounce is recorded as a traversal. For touchpad
164 // swipes, the amount of traversals in one animation depends on the amount of
165 // changes in the most visible desk have been seen. For other desk changes,
166 // the amount of traversals in one animation is 1 + number of Replace() calls.
167 // Multiple animations may be recorded before the timer stops.
168 class DesksController::DeskTraversalsMetricsHelper {
169 public:
DeskTraversalsMetricsHelper(DesksController * controller)170 explicit DeskTraversalsMetricsHelper(DesksController* controller)
171 : controller_(controller) {}
172 DeskTraversalsMetricsHelper(const DeskTraversalsMetricsHelper&) = delete;
173 DeskTraversalsMetricsHelper& operator=(const DeskTraversalsMetricsHelper&) =
174 delete;
175 ~DeskTraversalsMetricsHelper() = default;
176
177 // Starts |timer_| unless it is already running.
MaybeStart()178 void MaybeStart() {
179 if (timer_.IsRunning())
180 return;
181
182 count_ = 0;
183 timer_.Start(FROM_HERE, kDeskTraversalsTimeout,
184 base::BindOnce(&DeskTraversalsMetricsHelper::OnTimerStop,
185 base::Unretained(this)));
186 }
187
188 // Called when a desk animation is finished. Adds all observed
189 // |visible_desk_changes| to |count_|.
OnAnimationFinished(int visible_desk_changes)190 void OnAnimationFinished(int visible_desk_changes) {
191 if (timer_.IsRunning())
192 count_ += visible_desk_changes;
193 }
194
195 private:
OnTimerStop()196 void OnTimerStop() {
197 // If an animation is still running, add its current visible desk change
198 // count to |count_|.
199 DeskAnimationBase* current_animation = controller_->animation();
200 if (current_animation)
201 count_ += current_animation->visible_desk_changes();
202
203 base::UmaHistogramExactLinear(kNumberOfDeskTraversalsHistogramName, count_,
204 kDeskTraversalsMaxValue);
205 }
206
207 // Pointer to the DesksController that owns this. Guaranteed to be not
208 // nullptr for the lifetime of |this|.
209 DesksController* const controller_;
210
211 base::OneShotTimer timer_;
212
213 // Tracks the amount of traversals that have happened since |timer_| has
214 // started.
215 int count_ = 0;
216 };
217
DesksController()218 DesksController::DesksController()
219 : is_enhanced_desk_animations_(features::IsEnhancedDeskAnimations()),
220 metrics_helper_(std::make_unique<DeskTraversalsMetricsHelper>(this)) {
221 Shell::Get()->activation_client()->AddObserver(this);
222 Shell::Get()->session_controller()->AddObserver(this);
223
224 for (int id : desks_util::GetDesksContainersIds())
225 available_container_ids_.push(id);
226
227 // There's always one default desk. The DesksCreationRemovalSource used here
228 // doesn't matter, since UMA reporting will be skipped for the first ever
229 // default desk.
230 NewDesk(DesksCreationRemovalSource::kButton);
231 active_desk_ = desks_.back().get();
232 active_desk_->Activate(/*update_window_activation=*/true);
233 }
234
~DesksController()235 DesksController::~DesksController() {
236 Shell::Get()->session_controller()->RemoveObserver(this);
237 Shell::Get()->activation_client()->RemoveObserver(this);
238 }
239
240 // static
Get()241 DesksController* DesksController::Get() {
242 return Shell::Get()->desks_controller();
243 }
244
GetTargetActiveDesk() const245 const Desk* DesksController::GetTargetActiveDesk() const {
246 if (animation_)
247 return desks_[animation_->ending_desk_index()].get();
248 return active_desk();
249 }
250
RestorePrimaryUserActiveDeskIndex(int active_desk_index)251 void DesksController::RestorePrimaryUserActiveDeskIndex(int active_desk_index) {
252 DCHECK_GE(active_desk_index, 0);
253 DCHECK_LT(active_desk_index, int{desks_.size()});
254 user_to_active_desk_index_[Shell::Get()
255 ->session_controller()
256 ->GetPrimaryUserSession()
257 ->user_info.account_id] = active_desk_index;
258 // Following |OnActiveUserSessionChanged| approach, restoring uses
259 // DesksSwitchSource::kUserSwitch as a desk switch source.
260 // TODO(crbug.com/1145404): consider adding an UMA metric for desks
261 // restoring to change the source to kDeskRestored.
262 ActivateDesk(desks_[active_desk_index].get(), DesksSwitchSource::kUserSwitch);
263 }
264
Shutdown()265 void DesksController::Shutdown() {
266 animation_.reset();
267 }
268
AddObserver(Observer * observer)269 void DesksController::AddObserver(Observer* observer) {
270 observers_.AddObserver(observer);
271 }
272
RemoveObserver(Observer * observer)273 void DesksController::RemoveObserver(Observer* observer) {
274 observers_.RemoveObserver(observer);
275 }
276
AreDesksBeingModified() const277 bool DesksController::AreDesksBeingModified() const {
278 return are_desks_being_modified_ || !!animation_;
279 }
280
CanCreateDesks() const281 bool DesksController::CanCreateDesks() const {
282 return desks_.size() < desks_util::kMaxNumberOfDesks;
283 }
284
GetNextDesk(bool use_target_active_desk) const285 Desk* DesksController::GetNextDesk(bool use_target_active_desk) const {
286 int next_index = GetDeskIndex(use_target_active_desk ? GetTargetActiveDesk()
287 : active_desk_);
288 if (++next_index >= static_cast<int>(desks_.size()))
289 return nullptr;
290 return desks_[next_index].get();
291 }
292
GetPreviousDesk(bool use_target_active_desk) const293 Desk* DesksController::GetPreviousDesk(bool use_target_active_desk) const {
294 int previous_index = GetDeskIndex(
295 use_target_active_desk ? GetTargetActiveDesk() : active_desk_);
296 if (--previous_index < 0)
297 return nullptr;
298 return desks_[previous_index].get();
299 }
300
CanRemoveDesks() const301 bool DesksController::CanRemoveDesks() const {
302 return desks_.size() > 1;
303 }
304
NewDesk(DesksCreationRemovalSource source)305 void DesksController::NewDesk(DesksCreationRemovalSource source) {
306 DCHECK(CanCreateDesks());
307 DCHECK(!available_container_ids_.empty());
308
309 base::AutoReset<bool> in_progress(&are_desks_being_modified_, true);
310
311 // The first default desk should not overwrite any desks restore data, nor
312 // should it trigger any UMA stats reports.
313 const bool is_first_ever_desk = desks_.empty();
314
315 desks_.push_back(std::make_unique<Desk>(available_container_ids_.front()));
316 available_container_ids_.pop();
317 Desk* new_desk = desks_.back().get();
318 new_desk->SetName(GetDeskDefaultName(desks_.size() - 1),
319 /*set_by_user=*/false);
320
321 Shell::Get()
322 ->accessibility_controller()
323 ->TriggerAccessibilityAlertWithMessage(l10n_util::GetStringFUTF8(
324 IDS_ASH_VIRTUAL_DESKS_ALERT_NEW_DESK_CREATED,
325 base::NumberToString16(desks_.size())));
326
327 for (auto& observer : observers_)
328 observer.OnDeskAdded(new_desk);
329
330 if (!is_first_ever_desk) {
331 desks_restore_util::UpdatePrimaryUserDesksPrefs();
332 UMA_HISTOGRAM_ENUMERATION(kNewDeskHistogramName, source);
333 ReportDesksCountHistogram();
334 }
335 }
336
RemoveDesk(const Desk * desk,DesksCreationRemovalSource source)337 void DesksController::RemoveDesk(const Desk* desk,
338 DesksCreationRemovalSource source) {
339 DCHECK(CanRemoveDesks());
340 DCHECK(HasDesk(desk));
341
342 base::AutoReset<bool> in_progress(&are_desks_being_modified_, true);
343
344 auto* overview_controller = Shell::Get()->overview_controller();
345 const bool in_overview = overview_controller->InOverviewSession();
346 if (!in_overview && active_desk_ == desk) {
347 // When removing the active desk outside of overview, we trigger the remove
348 // desk animation. We will activate the desk to its left if any, otherwise,
349 // we activate one on the right.
350 const int current_desk_index = GetDeskIndex(active_desk_);
351 const int target_desk_index =
352 current_desk_index + ((current_desk_index > 0) ? -1 : 1);
353 DCHECK_GE(target_desk_index, 0);
354 DCHECK_LT(target_desk_index, static_cast<int>(desks_.size()));
355 animation_ = std::make_unique<DeskRemovalAnimation>(
356 this, current_desk_index, target_desk_index, source);
357 animation_->Launch();
358 return;
359 }
360
361 RemoveDeskInternal(desk, source);
362 }
363
ActivateDesk(const Desk * desk,DesksSwitchSource source)364 void DesksController::ActivateDesk(const Desk* desk, DesksSwitchSource source) {
365 DCHECK(HasDesk(desk));
366 DCHECK(!animation_);
367
368 OverviewController* overview_controller = Shell::Get()->overview_controller();
369 const bool in_overview = overview_controller->InOverviewSession();
370 if (desk == active_desk_) {
371 if (in_overview) {
372 // Selecting the active desk's mini_view in overview mode is allowed and
373 // should just exit overview mode normally.
374 overview_controller->EndOverview();
375 }
376 return;
377 }
378
379 UMA_HISTOGRAM_ENUMERATION(kDeskSwitchHistogramName, source);
380
381 const int target_desk_index = GetDeskIndex(desk);
382 if (source != DesksSwitchSource::kDeskRemoved) {
383 // Desk removal has its own a11y alert.
384 Shell::Get()
385 ->accessibility_controller()
386 ->TriggerAccessibilityAlertWithMessage(l10n_util::GetStringFUTF8(
387 IDS_ASH_VIRTUAL_DESKS_ALERT_DESK_ACTIVATED, desk->name()));
388 }
389
390 if (source == DesksSwitchSource::kDeskRemoved ||
391 source == DesksSwitchSource::kUserSwitch) {
392 // Desk switches due to desks removal or user switches in a multi-profile
393 // session result in immediate desk activation without animation.
394 ActivateDeskInternal(desk, /*update_window_activation=*/!in_overview);
395 return;
396 }
397
398 // When switching desks we want to update window activation when leaving
399 // overview or if nothing was active prior to switching desks. This will
400 // ensure that after switching desks, we will try to focus a candidate window.
401 // We will also update window activation if the currently active window is one
402 // in a switchable container. Otherwise, do not update the window activation.
403 // This will prevent some system UI windows like the app list from closing
404 // when switching desks.
405 aura::Window* active_window = window_util::GetActiveWindow();
406 const bool update_window_activation =
407 in_overview || !active_window ||
408 IsParentSwitchableContainer(active_window);
409 const int starting_desk_index = GetDeskIndex(active_desk());
410 animation_ = std::make_unique<DeskActivationAnimation>(
411 this, starting_desk_index, target_desk_index, source,
412 update_window_activation);
413 animation_->Launch();
414
415 metrics_helper_->MaybeStart();
416 }
417
ActivateAdjacentDesk(bool going_left,DesksSwitchSource source)418 bool DesksController::ActivateAdjacentDesk(bool going_left,
419 DesksSwitchSource source) {
420 // An on-going desk switch animation might be in progress. Skip this
421 // accelerator or touchpad event if enhanced desk animations are not enabled.
422 if (!is_enhanced_desk_animations_ && AreDesksBeingModified())
423 return false;
424
425 if (Shell::Get()->session_controller()->IsUserSessionBlocked())
426 return false;
427
428 // Try replacing an ongoing desk animation of the same source.
429 if (is_enhanced_desk_animations_ && animation_ &&
430 animation_->Replace(going_left, source)) {
431 return true;
432 }
433
434 const Desk* desk_to_activate = going_left ? GetPreviousDesk() : GetNextDesk();
435 if (desk_to_activate) {
436 ActivateDesk(desk_to_activate, source);
437 } else {
438 for (auto* root : Shell::GetAllRootWindows())
439 desks_animations::PerformHitTheWallAnimation(root, going_left);
440 }
441
442 return true;
443 }
444
StartSwipeAnimation(bool move_left)445 bool DesksController::StartSwipeAnimation(bool move_left) {
446 DCHECK(is_enhanced_desk_animations_);
447
448 // Activate an adjacent desk. It will replace an ongoing touchpad animation if
449 // one exists.
450 return ActivateAdjacentDesk(move_left,
451 DesksSwitchSource::kDeskSwitchTouchpad);
452 }
453
UpdateSwipeAnimation(float scroll_delta_x)454 void DesksController::UpdateSwipeAnimation(float scroll_delta_x) {
455 DCHECK(is_enhanced_desk_animations_);
456 if (animation_)
457 animation_->UpdateSwipeAnimation(scroll_delta_x);
458 }
459
EndSwipeAnimation()460 void DesksController::EndSwipeAnimation() {
461 DCHECK(is_enhanced_desk_animations_);
462 if (animation_)
463 animation_->EndSwipeAnimation();
464 }
465
MoveWindowFromActiveDeskTo(aura::Window * window,Desk * target_desk,aura::Window * target_root,DesksMoveWindowFromActiveDeskSource source)466 bool DesksController::MoveWindowFromActiveDeskTo(
467 aura::Window* window,
468 Desk* target_desk,
469 aura::Window* target_root,
470 DesksMoveWindowFromActiveDeskSource source) {
471 DCHECK_NE(active_desk_, target_desk);
472
473 // An active window might be an always-on-top or pip which doesn't belong to
474 // the active desk, and hence cannot be removed.
475 if (!base::Contains(active_desk_->windows(), window))
476 return false;
477
478 base::AutoReset<bool> in_progress(&are_desks_being_modified_, true);
479
480 auto* overview_controller = Shell::Get()->overview_controller();
481 const bool in_overview = overview_controller->InOverviewSession();
482
483 // The below order matters:
484 // If in overview, remove the item from overview first, before calling
485 // MoveWindowToDesk(), since MoveWindowToDesk() unminimizes the window (if it
486 // was minimized) before updating the mini views. We shouldn't change the
487 // window's minimized state before removing it from overview, since overview
488 // handles minimized windows differently.
489 if (in_overview) {
490 auto* overview_session = overview_controller->overview_session();
491 auto* item = overview_session->GetOverviewItemForWindow(window);
492 DCHECK(item);
493 item->OnMovingWindowToAnotherDesk();
494 // The item no longer needs to be in the overview grid.
495 overview_session->RemoveItem(item);
496 }
497
498 active_desk_->MoveWindowToDesk(window, target_desk, target_root);
499
500 MaybeUpdateShelfItems(/*windows_on_inactive_desk=*/{window},
501 /*windows_on_active_desk=*/{});
502
503 Shell::Get()
504 ->accessibility_controller()
505 ->TriggerAccessibilityAlertWithMessage(l10n_util::GetStringFUTF8(
506 IDS_ASH_VIRTUAL_DESKS_ALERT_WINDOW_MOVED_FROM_ACTIVE_DESK,
507 window->GetTitle(), active_desk_->name(), target_desk->name()));
508
509 UMA_HISTOGRAM_ENUMERATION(kMoveWindowFromActiveDeskHistogramName, source);
510 ReportNumberOfWindowsPerDeskHistogram();
511
512 // A window moving out of the active desk cannot be active.
513 // If we are in overview, we should not change the window activation as we do
514 // below, since the dummy "OverviewModeFocusedWidget" should remain active
515 // while overview mode is active.
516 if (!in_overview)
517 wm::DeactivateWindow(window);
518 return true;
519 }
520
RevertDeskNameToDefault(Desk * desk)521 void DesksController::RevertDeskNameToDefault(Desk* desk) {
522 DCHECK(HasDesk(desk));
523 desk->SetName(GetDeskDefaultName(GetDeskIndex(desk)), /*set_by_user=*/false);
524 }
525
RestoreNameOfDeskAtIndex(base::string16 name,size_t index)526 void DesksController::RestoreNameOfDeskAtIndex(base::string16 name,
527 size_t index) {
528 DCHECK(!name.empty());
529 DCHECK_LT(index, desks_.size());
530
531 desks_[index]->SetName(std::move(name), /*set_by_user=*/true);
532 }
533
OnRootWindowAdded(aura::Window * root_window)534 void DesksController::OnRootWindowAdded(aura::Window* root_window) {
535 for (auto& desk : desks_)
536 desk->OnRootWindowAdded(root_window);
537 }
538
OnRootWindowClosing(aura::Window * root_window)539 void DesksController::OnRootWindowClosing(aura::Window* root_window) {
540 for (auto& desk : desks_)
541 desk->OnRootWindowClosing(root_window);
542 }
543
GetDeskIndex(const Desk * desk) const544 int DesksController::GetDeskIndex(const Desk* desk) const {
545 for (size_t i = 0; i < desks_.size(); ++i) {
546 if (desk == desks_[i].get())
547 return i;
548 }
549
550 NOTREACHED();
551 return -1;
552 }
553
BelongsToActiveDesk(aura::Window * window)554 bool DesksController::BelongsToActiveDesk(aura::Window* window) {
555 return desks_util::BelongsToActiveDesk(window);
556 }
557
OnWindowActivating(ActivationReason reason,aura::Window * gaining_active,aura::Window * losing_active)558 void DesksController::OnWindowActivating(ActivationReason reason,
559 aura::Window* gaining_active,
560 aura::Window* losing_active) {
561 if (AreDesksBeingModified())
562 return;
563
564 if (!gaining_active)
565 return;
566
567 const Desk* window_desk = FindDeskOfWindow(gaining_active);
568 if (!window_desk || window_desk == active_desk_)
569 return;
570
571 ActivateDesk(window_desk, DesksSwitchSource::kWindowActivated);
572 }
573
OnWindowActivated(ActivationReason reason,aura::Window * gained_active,aura::Window * lost_active)574 void DesksController::OnWindowActivated(ActivationReason reason,
575 aura::Window* gained_active,
576 aura::Window* lost_active) {}
577
OnActiveUserSessionChanged(const AccountId & account_id)578 void DesksController::OnActiveUserSessionChanged(const AccountId& account_id) {
579 // TODO(afakhry): Remove this when multi-profile support goes away.
580 DCHECK(current_account_id_.is_valid());
581 if (current_account_id_ == account_id) {
582 return;
583 }
584
585 user_to_active_desk_index_[current_account_id_] = GetDeskIndex(active_desk_);
586 current_account_id_ = account_id;
587
588 // Note the following constraints for secondary users:
589 // - Simultaneously logged-in users share the same number of desks.
590 // - We don't sync and restore the number of desks nor the active desk
591 // position from previous login sessions.
592 //
593 // Given the above, we do the following for simplicity:
594 // - If this user has never been seen before, we activate their first desk.
595 // - If one of the simultaneously logged-in users remove desks, that other
596 // users' active-desk indices may become invalid. We won't create extra
597 // desks for this user, but rather we will simply activate their last desk
598 // on the right. Future user switches will update the pref for this user to
599 // the correct value.
600 int new_user_active_desk_index =
601 /* This is a default initialized index to 0 if the id doesn't exist. */
602 user_to_active_desk_index_[current_account_id_];
603 new_user_active_desk_index = base::ClampToRange(
604 new_user_active_desk_index, 0, static_cast<int>(desks_.size()) - 1);
605
606 ActivateDesk(desks_[new_user_active_desk_index].get(),
607 DesksSwitchSource::kUserSwitch);
608 }
609
OnFirstSessionStarted()610 void DesksController::OnFirstSessionStarted() {
611 current_account_id_ =
612 Shell::Get()->session_controller()->GetActiveAccountId();
613 desks_restore_util::RestorePrimaryUserDesks();
614 }
615
OnAnimationFinished(DeskAnimationBase * animation)616 void DesksController::OnAnimationFinished(DeskAnimationBase* animation) {
617 DCHECK_EQ(animation_.get(), animation);
618 metrics_helper_->OnAnimationFinished(animation->visible_desk_changes());
619 animation_.reset();
620 }
621
HasDesk(const Desk * desk) const622 bool DesksController::HasDesk(const Desk* desk) const {
623 auto iter = std::find_if(
624 desks_.begin(), desks_.end(),
625 [desk](const std::unique_ptr<Desk>& d) { return d.get() == desk; });
626 return iter != desks_.end();
627 }
628
ActivateDeskInternal(const Desk * desk,bool update_window_activation)629 void DesksController::ActivateDeskInternal(const Desk* desk,
630 bool update_window_activation) {
631 DCHECK(HasDesk(desk));
632
633 if (desk == active_desk_)
634 return;
635
636 base::AutoReset<bool> in_progress(&are_desks_being_modified_, true);
637
638 // Mark the new desk as active first, so that deactivating windows on the
639 // `old_active` desk do not activate other windows on the same desk. See
640 // `ash::AshFocusRules::GetNextActivatableWindow()`.
641 Desk* old_active = active_desk_;
642 active_desk_ = const_cast<Desk*>(desk);
643
644 // There should always be an active desk at any time.
645 DCHECK(old_active);
646 old_active->Deactivate(update_window_activation);
647 active_desk_->Activate(update_window_activation);
648
649 MaybeUpdateShelfItems(old_active->windows(), active_desk_->windows());
650
651 // If in the middle of a window cycle gesture, reset the window cycle list
652 // contents so it contains the new active desk's windows.
653 auto* shell = Shell::Get();
654 if (features::IsAltTabLimitedToActiveDesk()) {
655 auto* window_cycle_controller = shell->window_cycle_controller();
656 window_cycle_controller->MaybeResetCycleList();
657 }
658
659 for (auto& observer : observers_)
660 observer.OnDeskActivationChanged(active_desk_, old_active);
661
662 // Only update active desk prefs when a primary user switches a desk.
663 if (features::IsDesksRestoreEnabled() &&
664 shell->session_controller()->IsUserPrimary()) {
665 desks_restore_util::UpdatePrimaryUserActiveDeskPrefs(
666 GetDeskIndex(active_desk_));
667 }
668 }
669
RemoveDeskInternal(const Desk * desk,DesksCreationRemovalSource source)670 void DesksController::RemoveDeskInternal(const Desk* desk,
671 DesksCreationRemovalSource source) {
672 DCHECK(CanRemoveDesks());
673
674 base::AutoReset<bool> in_progress(&are_desks_being_modified_, true);
675
676 auto iter = std::find_if(
677 desks_.begin(), desks_.end(),
678 [desk](const std::unique_ptr<Desk>& d) { return d.get() == desk; });
679 DCHECK(iter != desks_.end());
680
681 // Used by accessibility to indicate the desk that has been removed.
682 const int removed_desk_number = std::distance(desks_.begin(), iter) + 1;
683
684 // Keep the removed desk alive until the end of this function.
685 std::unique_ptr<Desk> removed_desk = std::move(*iter);
686 DCHECK_EQ(removed_desk.get(), desk);
687 auto iter_after = desks_.erase(iter);
688
689 DCHECK(!desks_.empty());
690
691 auto* overview_controller = Shell::Get()->overview_controller();
692 const bool in_overview = overview_controller->InOverviewSession();
693 const std::vector<aura::Window*> removed_desk_windows =
694 removed_desk->windows();
695
696 // No need to spend time refreshing the mini_views of the removed desk.
697 auto removed_desk_mini_views_pauser =
698 removed_desk->GetScopedNotifyContentChangedDisabler();
699
700 // - Move windows in removed desk (if any) to the currently active desk.
701 // - If the active desk is the one being removed, activate the desk to its
702 // left, if no desk to the left, activate one on the right.
703 const bool will_switch_desks = (removed_desk.get() == active_desk_);
704 if (!will_switch_desks) {
705 // We will refresh the mini_views of the active desk only once at the end.
706 auto active_desk_mini_view_pauser =
707 active_desk_->GetScopedNotifyContentChangedDisabler();
708
709 removed_desk->MoveWindowsToDesk(active_desk_);
710
711 MaybeUpdateShelfItems({}, removed_desk_windows);
712
713 // If overview mode is active, we add the windows of the removed desk to the
714 // overview grid in the order of the new MRU (which changes after removing a
715 // desk by making the windows of the removed desk as the least recently used
716 // across all desks). Note that this can only be done after the windows have
717 // moved to the active desk in `MoveWindowsToDesk()` above, so that building
718 // the window MRU list should contain those windows.
719 if (in_overview)
720 AppendWindowsToOverview(removed_desk_windows);
721 } else {
722 Desk* target_desk = nullptr;
723 if (iter_after == desks_.begin()) {
724 // Nothing before this desk.
725 target_desk = (*iter_after).get();
726 } else {
727 // Back up to select the desk on the left.
728 target_desk = (*(--iter_after)).get();
729 }
730
731 DCHECK(target_desk);
732
733 // The target desk, which is about to become active, will have its
734 // mini_views refreshed at the end.
735 auto target_desk_mini_view_pauser =
736 target_desk->GetScopedNotifyContentChangedDisabler();
737
738 // Exit split view if active, before activating the new desk. We will
739 // restore the split view state of the newly activated desk at the end.
740 for (aura::Window* root_window : Shell::GetAllRootWindows()) {
741 SplitViewController::Get(root_window)
742 ->EndSplitView(SplitViewController::EndReason::kDesksChange);
743 }
744
745 // The removed desk is still the active desk, so temporarily remove its
746 // windows from the overview grid which will result in removing the
747 // "OverviewModeLabel" widgets created by overview mode for these windows.
748 // This way the removed desk tracks only real windows, which are now ready
749 // to be moved to the target desk.
750 if (in_overview)
751 RemoveAllWindowsFromOverview();
752
753 // If overview mode is active, change desk activation without changing
754 // window activation. Activation should remain on the dummy
755 // "OverviewModeFocusedWidget" while overview mode is active.
756 removed_desk->MoveWindowsToDesk(target_desk);
757 ActivateDesk(target_desk, DesksSwitchSource::kDeskRemoved);
758
759 // Desk activation should not change overview mode state.
760 DCHECK_EQ(in_overview, overview_controller->InOverviewSession());
761
762 // Now that the windows from the removed and target desks merged, add them
763 // all to the grid in the order of the new MRU.
764 if (in_overview)
765 AppendWindowsToOverview(target_desk->windows());
766 }
767
768 // It's OK now to refresh the mini_views of *only* the active desk, and only
769 // if windows from the removed desk moved to it.
770 DCHECK(active_desk_->should_notify_content_changed());
771 if (!removed_desk_windows.empty())
772 active_desk_->NotifyContentChanged();
773
774 UpdateDesksDefaultNames();
775
776 for (auto& observer : observers_)
777 observer.OnDeskRemoved(removed_desk.get());
778
779 available_container_ids_.push(removed_desk->container_id());
780
781 // Avoid having stale backdrop state as a desk is removed while in overview
782 // mode, since the backdrop controller won't update the backdrop window as
783 // the removed desk's windows move out from the container. Therefore, we need
784 // to update it manually.
785 if (in_overview)
786 removed_desk->UpdateDeskBackdrops();
787
788 // Restoring split view may start or end overview mode, therefore do this at
789 // the end to avoid getting into a bad state.
790 if (will_switch_desks)
791 MaybeRestoreSplitView(/*refresh_snapped_windows=*/true);
792
793 UMA_HISTOGRAM_ENUMERATION(kRemoveDeskHistogramName, source);
794 ReportDesksCountHistogram();
795 ReportNumberOfWindowsPerDeskHistogram();
796
797 int active_desk_number = GetDeskIndex(active_desk_) + 1;
798 if (active_desk_number == removed_desk_number)
799 active_desk_number++;
800 Shell::Get()
801 ->accessibility_controller()
802 ->TriggerAccessibilityAlertWithMessage(l10n_util::GetStringFUTF8(
803 IDS_ASH_VIRTUAL_DESKS_ALERT_DESK_REMOVED, removed_desk->name(),
804 active_desk_->name()));
805
806 desks_restore_util::UpdatePrimaryUserDesksPrefs();
807
808 DCHECK_LE(available_container_ids_.size(), desks_util::kMaxNumberOfDesks);
809 }
810
FindDeskOfWindow(aura::Window * window) const811 const Desk* DesksController::FindDeskOfWindow(aura::Window* window) const {
812 DCHECK(window);
813
814 for (const auto& desk : desks_) {
815 if (base::Contains(desk->windows(), window))
816 return desk.get();
817 }
818
819 return nullptr;
820 }
821
ReportNumberOfWindowsPerDeskHistogram() const822 void DesksController::ReportNumberOfWindowsPerDeskHistogram() const {
823 for (size_t i = 0; i < desks_.size(); ++i) {
824 const size_t windows_count = desks_[i]->windows().size();
825 switch (i) {
826 case 0:
827 UMA_HISTOGRAM_COUNTS_100(kNumberOfWindowsOnDesk_1_HistogramName,
828 windows_count);
829 break;
830
831 case 1:
832 UMA_HISTOGRAM_COUNTS_100(kNumberOfWindowsOnDesk_2_HistogramName,
833 windows_count);
834 break;
835
836 case 2:
837 UMA_HISTOGRAM_COUNTS_100(kNumberOfWindowsOnDesk_3_HistogramName,
838 windows_count);
839 break;
840
841 case 3:
842 UMA_HISTOGRAM_COUNTS_100(kNumberOfWindowsOnDesk_4_HistogramName,
843 windows_count);
844 break;
845
846 default:
847 NOTREACHED();
848 break;
849 }
850 }
851 }
852
ReportDesksCountHistogram() const853 void DesksController::ReportDesksCountHistogram() const {
854 DCHECK_LE(desks_.size(), desks_util::kMaxNumberOfDesks);
855 UMA_HISTOGRAM_EXACT_LINEAR(kDesksCountHistogramName, desks_.size(),
856 desks_util::kMaxNumberOfDesks);
857 }
858
UpdateDesksDefaultNames()859 void DesksController::UpdateDesksDefaultNames() {
860 size_t i = 0;
861 for (auto& desk : desks_) {
862 // Do not overwrite user-modified desks' names.
863 if (!desk->is_name_set_by_user())
864 desk->SetName(GetDeskDefaultName(i), /*set_by_user=*/false);
865 i++;
866 }
867 }
868
869 } // namespace ash
870