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