[go: nahoru, domu]

blob: e2399bfeddb13adcd2db058f7123d8307e657259 [file] [log] [blame]
chinsenj12a1104d2021-01-08 00:30:281// Copyright 2018 The Chromium Authors. All rights reserved.
Ahmed Fakhryec3483792019-03-04 18:09:272// 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/desk.h"
6
Ahmed Fakhry7664e912019-06-19 16:56:267#include <algorithm>
Ahmed Fakhryeb7375b22019-04-24 22:47:368#include <utility>
9
Ahmed Fakhry38e118a2019-08-28 19:55:4010#include "ash/public/cpp/app_types.h"
Cattalyya Nuengsigkapian8c45e302021-01-06 21:48:0811#include "ash/public/cpp/ash_features.h"
Ahmed Fakhrybaac2b312020-02-19 21:21:5512#include "ash/public/cpp/shell_window_ids.h"
Ahmed Fakhry9df7b432019-05-15 17:02:2413#include "ash/public/cpp/window_properties.h"
Ahmed Fakhryeb7375b22019-04-24 22:47:3614#include "ash/shell.h"
Cattalyya Nuengsigkapian8c45e302021-01-06 21:48:0815#include "ash/wm/desks/desks_controller.h"
Ahmed Fakhryfc2ca392021-01-26 17:59:2516#include "ash/wm/desks/desks_util.h"
Ahmed Fakhryeb7375b22019-04-24 22:47:3617#include "ash/wm/mru_window_tracker.h"
Ahmed3892c172020-03-24 17:54:3618#include "ash/wm/overview/overview_controller.h"
Ahmed Fakhry6d5990922019-07-10 01:16:5019#include "ash/wm/window_positioner.h"
Ahmed Fakhry00f778472019-05-08 00:50:0920#include "ash/wm/window_state.h"
Ahmed Fakhryeb7375b22019-04-24 22:47:3621#include "ash/wm/window_transient_descendant_iterator.h"
22#include "ash/wm/window_util.h"
Ahmed Fakhry756d77a2019-05-31 23:21:2323#include "ash/wm/workspace/backdrop_controller.h"
24#include "ash/wm/workspace/workspace_layout_manager.h"
25#include "ash/wm/workspace_controller.h"
Ahmed Fakhry3d28cb42020-02-27 21:37:0426#include "base/containers/adapters.h"
Jan Wilken Dörrieb5a41c32020-12-09 18:55:4727#include "base/containers/contains.h"
chinsenj932efe342021-03-06 06:17:1428#include "base/macros.h"
chinsenj7936a652021-03-04 18:26:2629#include "base/metrics/histogram_functions.h"
chinsenj932efe342021-03-06 06:17:1430#include "base/no_destructor.h"
Ahmed Fakhry7664e912019-06-19 16:56:2631#include "base/stl_util.h"
Cattalyya Nuengsigkapian8c45e302021-01-06 21:48:0832#include "chromeos/ui/base/window_properties.h"
Ahmed Fakhry38e118a2019-08-28 19:55:4033#include "ui/aura/client/aura_constants.h"
Ahmed Fakhry3d28cb42020-02-27 21:37:0434#include "ui/aura/window_tracker.h"
Ahmed Fakhryd984db512020-03-13 22:02:2435#include "ui/display/screen.h"
Ahmed Fakhryc3368982019-05-30 23:00:4436#include "ui/wm/core/window_util.h"
Ahmed Fakhryeb7375b22019-04-24 22:47:3637
Ahmed Fakhryec3483792019-03-04 18:09:2738namespace ash {
39
Ahmed Fakhry756d77a2019-05-31 23:21:2340namespace {
41
chinsenj932efe342021-03-06 06:17:1442// The name of the histogram for consecutive daily visits.
43constexpr char kConsecutiveDailyVisitsHistogramName[] =
44 "Ash.Desks.ConsecutiveDailyVisits";
45
chinsenj7936a652021-03-04 18:26:2646// Prefix for the desks lifetime histogram.
47constexpr char kDeskLifetimeHistogramNamePrefix[] = "Ash.Desks.DeskLifetime_";
48
Ahmed Fakhry756d77a2019-05-31 23:21:2349void UpdateBackdropController(aura::Window* desk_container) {
50 auto* workspace_controller = GetWorkspaceController(desk_container);
Ahmed Fakhry7664e912019-06-19 16:56:2651 // Work might have already been cleared when the display is removed. See
52 // |RootWindowController::MoveWindowsTo()|.
53 if (!workspace_controller)
54 return;
55
Ahmed Fakhry756d77a2019-05-31 23:21:2356 WorkspaceLayoutManager* layout_manager =
57 workspace_controller->layout_manager();
58 BackdropController* backdrop_controller =
59 layout_manager->backdrop_controller();
60 backdrop_controller->OnDeskContentChanged();
61}
62
63// Returns true if |window| can be managed by the desk, and therefore can be
64// moved out of the desk when the desk is removed.
65bool CanMoveWindowOutOfDeskContainer(aura::Window* window) {
Ahmed Fakhrybaac2b312020-02-19 21:21:5566 // The desks bar widget is an activatable window placed in the active desk's
67 // container, therefore it should be allowed to move outside of its desk when
68 // its desk is removed.
69 if (window->id() == kShellWindowId_DesksBarWindow)
70 return true;
71
Ahmed Fakhry7664e912019-06-19 16:56:2672 // We never move transient descendants directly, this is taken care of by
73 // `wm::TransientWindowManager::OnWindowHierarchyChanged()`.
74 auto* transient_root = ::wm::GetTransientRoot(window);
75 if (transient_root != window)
76 return false;
77
Ahmed Fakhry38e118a2019-08-28 19:55:4078 // Only allow app windows to move to other desks.
79 return window->GetProperty(aura::client::kAppType) !=
80 static_cast<int>(AppType::NON_APP);
Ahmed Fakhry756d77a2019-05-31 23:21:2381}
82
Ahmed Fakhryfc2ca392021-01-26 17:59:2583// Adjusts the z-order stacking of |window_to_fix| in its parent to match its
84// order in the MRU window list. This is done after the window is moved from one
85// desk container to another by means of calling AddChild() which adds it as the
86// top-most window, which doesn't necessarily match the MRU order.
87// |window_to_fix| must be a child of a desk container, and the root of a
88// transient hierarchy (if it belongs to one).
89// This function must be called AddChild() was called to add the |window_to_fix|
90// (i.e. |window_to_fix| is the top-most window or the top-most window is a
91// transient child of |window_to_fix|).
92void FixWindowStackingAccordingToGlobalMru(aura::Window* window_to_fix) {
93 aura::Window* container = window_to_fix->parent();
94 DCHECK(desks_util::IsDeskContainer(container));
95 DCHECK_EQ(window_to_fix, wm::GetTransientRoot(window_to_fix));
96 DCHECK(window_to_fix == container->children().back() ||
97 window_to_fix == wm::GetTransientRoot(container->children().back()));
98
99 const auto mru_windows =
100 Shell::Get()->mru_window_tracker()->BuildWindowListIgnoreModal(
101 DesksMruType::kAllDesks);
102 // Find the closest sibling that is not a transient descendant, which
103 // |window_to_fix| should be stacked below.
104 aura::Window* closest_sibling_above_window = nullptr;
105 for (auto* window : mru_windows) {
106 if (window == window_to_fix) {
107 if (closest_sibling_above_window)
108 container->StackChildBelow(window_to_fix, closest_sibling_above_window);
109 return;
110 }
111
112 if (window->parent() == container &&
113 !wm::HasTransientAncestor(window, window_to_fix)) {
114 closest_sibling_above_window = window;
115 }
116 }
117}
118
chinsenj932efe342021-03-06 06:17:14119// Returns Jan 1, 2010 00:00:00 as a base::Time object in the local timezone.
120base::Time GetLocalEpoch() {
121 static base::NoDestructor<base::Time> local_epoch;
122 if (local_epoch->is_null()) {
123 ignore_result(base::Time::FromLocalExploded({2010, 1, 5, 1, 0, 0, 0, 0},
124 local_epoch.get()));
125 }
126 return *local_epoch;
127}
128
Ahmed Fakhry6d5990922019-07-10 01:16:50129// Used to temporarily turn off the automatic window positioning while windows
130// are being moved between desks.
131class ScopedWindowPositionerDisabler {
132 public:
133 ScopedWindowPositionerDisabler() {
134 WindowPositioner::DisableAutoPositioning(true);
135 }
136
137 ~ScopedWindowPositionerDisabler() {
138 WindowPositioner::DisableAutoPositioning(false);
139 }
140
141 private:
142 DISALLOW_COPY_AND_ASSIGN(ScopedWindowPositionerDisabler);
143};
144
Ahmed Fakhry756d77a2019-05-31 23:21:23145} // namespace
146
Ahmed Fakhryeb7375b22019-04-24 22:47:36147class DeskContainerObserver : public aura::WindowObserver {
148 public:
149 DeskContainerObserver(Desk* owner, aura::Window* container)
150 : owner_(owner), container_(container) {
151 DCHECK_EQ(container_->id(), owner_->container_id());
152 container->AddObserver(this);
153 }
154
155 ~DeskContainerObserver() override { container_->RemoveObserver(this); }
156
157 // aura::WindowObserver:
158 void OnWindowAdded(aura::Window* new_window) override {
159 // TODO(afakhry): Overview mode creates a new widget for each window under
Sammie Quon64596be32019-10-28 21:53:00160 // the same parent for the OverviewItemView. We will be notified with
Ahmed Fakhryce8bf1d822019-04-30 22:06:24161 // this window addition here. Consider ignoring these windows if they cause
162 // problems.
Ahmed Fakhryeb7375b22019-04-24 22:47:36163 owner_->AddWindowToDesk(new_window);
chinsenj560df072021-02-16 21:16:36164 MaybeNotifyAllDesksOfContentChange(new_window);
Ahmed Fakhryeb7375b22019-04-24 22:47:36165 }
166
Ahmed Fakhry7664e912019-06-19 16:56:26167 void OnWindowRemoved(aura::Window* removed_window) override {
168 // We listen to `OnWindowRemoved()` as opposed to `OnWillRemoveWindow()`
169 // since we want to refresh the mini_views only after the window has been
170 // removed from the window tree hierarchy.
171 owner_->RemoveWindowFromDesk(removed_window);
chinsenj560df072021-02-16 21:16:36172 MaybeNotifyAllDesksOfContentChange(removed_window);
Ahmed Fakhry7664e912019-06-19 16:56:26173 }
174
Ahmed Fakhryeb7375b22019-04-24 22:47:36175 void OnWindowDestroyed(aura::Window* window) override {
176 // We should never get here. We should be notified in
177 // `OnRootWindowClosing()` before the child containers of the root window
178 // are destroyed, and this object should have already been destroyed.
179 NOTREACHED();
180 }
181
182 private:
chinsenj560df072021-02-16 21:16:36183 void MaybeNotifyAllDesksOfContentChange(aura::Window* window) {
184 // If a visible on all desks window is added/removed from a desk, only the
185 // desks directly involved will know about their contents changing since it
186 // only resides on the active desk. Since visible on all desks windows
187 // appear in each desks' preview view, we need to notify each desk.
188 auto* desks_controller = DesksController::Get();
189 if (desks_controller->visible_on_all_desks_windows().contains(window))
190 desks_controller->NotifyAllDesksForContentChanged();
191 }
192
Ahmed Fakhryeb7375b22019-04-24 22:47:36193 Desk* const owner_;
194 aura::Window* const container_;
195
196 DISALLOW_COPY_AND_ASSIGN(DeskContainerObserver);
197};
198
199// -----------------------------------------------------------------------------
200// Desk:
201
202Desk::Desk(int associated_container_id)
chinsenj7936a652021-03-04 18:26:26203 : container_id_(associated_container_id),
204 creation_time_(base::Time::Now()) {
Ahmed Fakhryeb7375b22019-04-24 22:47:36205 // For the very first default desk added during initialization, there won't be
206 // any root windows yet. That's OK, OnRootWindowAdded() will be called
207 // explicitly by the RootWindowController when they're initialized.
208 for (aura::Window* root : Shell::GetAllRootWindows())
209 OnRootWindowAdded(root);
210}
211
212Desk::~Desk() {
Ahmed Fakhry7664e912019-06-19 16:56:26213#if DCHECK_IS_ON()
Ahmed Fakhry756d77a2019-05-31 23:21:23214 for (auto* window : windows_) {
215 DCHECK(!CanMoveWindowOutOfDeskContainer(window))
216 << "DesksController should remove this desk's application windows "
217 "first.";
Ahmed Fakhry756d77a2019-05-31 23:21:23218 }
Ahmed Fakhry7664e912019-06-19 16:56:26219#endif
Ahmed Fakhry9df7b432019-05-15 17:02:24220
221 for (auto& observer : observers_) {
222 observers_.RemoveObserver(&observer);
223 observer.OnDeskDestroyed(this);
224 }
Ahmed Fakhryeb7375b22019-04-24 22:47:36225}
226
Ahmed Fakhry927eabc2019-05-11 00:14:15227void Desk::AddObserver(Observer* observer) {
228 observers_.AddObserver(observer);
229}
230
231void Desk::RemoveObserver(Observer* observer) {
232 observers_.RemoveObserver(observer);
233}
234
Ahmed Fakhryeb7375b22019-04-24 22:47:36235void Desk::OnRootWindowAdded(aura::Window* root) {
236 DCHECK(!roots_to_containers_observers_.count(root));
237
Ahmed Fakhry7664e912019-06-19 16:56:26238 // No windows should be added to the desk container on |root| prior to
239 // tracking it by the desk.
Ahmed Fakhryeb7375b22019-04-24 22:47:36240 aura::Window* desk_container = root->GetChildById(container_id_);
Ahmed Fakhry7664e912019-06-19 16:56:26241 DCHECK(desk_container->children().empty());
Ahmed Fakhryeb7375b22019-04-24 22:47:36242 auto container_observer =
243 std::make_unique<DeskContainerObserver>(this, desk_container);
244 roots_to_containers_observers_.emplace(root, std::move(container_observer));
245}
246
247void Desk::OnRootWindowClosing(aura::Window* root) {
248 const size_t count = roots_to_containers_observers_.erase(root);
249 DCHECK(count);
Ahmed Fakhrya0f9755b2019-07-24 23:06:54250
251 // The windows on this root are about to be destroyed. We already stopped
252 // observing the container above, so we won't get a call to
253 // DeskContainerObserver::OnWindowRemoved(). Therefore, we must remove those
Ahmed Fakhry57835ef2019-07-25 18:01:16254 // windows manually. If this is part of shutdown (i.e. when the
255 // RootWindowController is being destroyed), then we're done with those
256 // windows. If this is due to a display being removed, then the
257 // WindowTreeHostManager will move those windows to another host/root, and
258 // they will be added again to the desk container on the new root.
259 const auto windows = windows_;
260 for (auto* window : windows) {
261 if (window->GetRootWindow() == root)
262 base::Erase(windows_, window);
263 }
Ahmed Fakhryeb7375b22019-04-24 22:47:36264}
265
266void Desk::AddWindowToDesk(aura::Window* window) {
Ahmed Fakhry7664e912019-06-19 16:56:26267 DCHECK(!base::Contains(windows_, window));
268 windows_.push_back(window);
269 // No need to refresh the mini_views if the destroyed window doesn't show up
chinsenj560df072021-02-16 21:16:36270 // there in the first place. Also don't refresh for visible on all desks
271 // windows since they're already refreshed in OnWindowAdded().
272 if (!window->GetProperty(kHideInDeskMiniViewKey) &&
273 !window->GetProperty(aura::client::kVisibleOnAllWorkspacesKey)) {
Ahmed3892c172020-03-24 17:54:36274 NotifyContentChanged();
chinsenj560df072021-02-16 21:16:36275 }
Cattalyya Nuengsigkapian8c45e302021-01-06 21:48:08276
277 // Update the window's workspace to this parent desk.
Sammie Quonb0a7ed552021-02-11 02:40:58278 if ((features::IsBentoEnabled() || features::IsFullRestoreEnabled()) &&
279 !is_desk_being_removed_) {
Cattalyya Nuengsigkapian8c45e302021-01-06 21:48:08280 auto* desks_controller = DesksController::Get();
281 window->SetProperty(aura::client::kWindowWorkspaceKey,
282 desks_controller->GetDeskIndex(this));
283 }
Ahmed Fakhry7664e912019-06-19 16:56:26284}
Ahmed Fakhryeb7375b22019-04-24 22:47:36285
Ahmed Fakhry7664e912019-06-19 16:56:26286void Desk::RemoveWindowFromDesk(aura::Window* window) {
287 DCHECK(base::Contains(windows_, window));
288 base::Erase(windows_, window);
289 // No need to refresh the mini_views if the destroyed window doesn't show up
chinsenj560df072021-02-16 21:16:36290 // there in the first place. Also don't refresh for visible on all desks
291 // windows since they're already refreshed in OnWindowRemoved().
292 if (!window->GetProperty(kHideInDeskMiniViewKey) &&
293 !window->GetProperty(aura::client::kVisibleOnAllWorkspacesKey)) {
Ahmed3892c172020-03-24 17:54:36294 NotifyContentChanged();
chinsenj560df072021-02-16 21:16:36295 }
Ahmed Fakhry9df7b432019-05-15 17:02:24296}
297
Ahmed Fakhry3571b4f52019-05-15 20:55:49298base::AutoReset<bool> Desk::GetScopedNotifyContentChangedDisabler() {
299 return base::AutoReset<bool>(&should_notify_content_changed_, false);
Ahmed Fakhryeb7375b22019-04-24 22:47:36300}
301
Jan Wilken Dörrie85285b02021-03-11 23:38:47302void Desk::SetName(std::u16string new_name, bool set_by_user) {
Ahmed Fakhry5d041ef2020-02-20 21:39:05303 // Even if the user focuses the DeskNameView for the first time and hits enter
304 // without changing the desk's name (i.e. |new_name| is the same,
305 // |is_name_set_by_user_| is false, and |set_by_user| is true), we don't
306 // change |is_name_set_by_user_| and keep considering the name as a default
307 // name.
308 if (name_ == new_name)
309 return;
310
Ahmed Fakhrya1c9db72020-01-29 03:14:53311 name_ = std::move(new_name);
Ahmed Fakhry5d041ef2020-02-20 21:39:05312 is_name_set_by_user_ = set_by_user;
Ahmed Fakhrya1c9db72020-01-29 03:14:53313
314 for (auto& observer : observers_)
315 observer.OnDeskNameChanged(name_);
316}
317
Ahmed Fakhry2bc12682020-02-08 00:20:11318void Desk::PrepareForActivationAnimation() {
319 DCHECK(!is_active_);
320
321 for (aura::Window* root : Shell::GetAllRootWindows()) {
322 auto* container = root->GetChildById(container_id_);
323 container->layer()->SetOpacity(0);
324 container->Show();
325 }
326 started_activation_animation_ = true;
327}
328
Ahmed Fakhryce8bf1d822019-04-30 22:06:24329void Desk::Activate(bool update_window_activation) {
Ahmed Fakhry2bc12682020-02-08 00:20:11330 DCHECK(!is_active_);
331
332 if (!MaybeResetContainersOpacities()) {
333 for (aura::Window* root : Shell::GetAllRootWindows())
334 root->GetChildById(container_id_)->Show();
335 }
Ahmed Fakhryeb7375b22019-04-24 22:47:36336
Ahmed Fakhryce8bf1d822019-04-30 22:06:24337 is_active_ = true;
338
chinsenj932efe342021-03-06 06:17:14339 if (!IsConsecutiveDailyVisit())
340 RecordAndResetConsecutiveDailyVisits(/*being_removed=*/false);
341
342 int current_date = GetDaysFromLocalEpoch();
343 if (current_date < last_day_visited_ || first_day_visited_ == -1) {
344 // If |current_date| < |last_day_visited_| then the user has moved timezones
345 // or the stored data has been corrupted so reset |first_day_visited_|.
346 first_day_visited_ = current_date;
347 }
348 last_day_visited_ = current_date;
349
Ahmed Fakhryce8bf1d822019-04-30 22:06:24350 if (!update_window_activation || windows_.empty())
Ahmed Fakhryeb7375b22019-04-24 22:47:36351 return;
352
353 // Activate the window on this desk that was most recently used right before
354 // the user switched to another desk, so as not to break the user's workflow.
355 for (auto* window :
Ahmed Fakhryc3368982019-05-30 23:00:44356 Shell::Get()->mru_window_tracker()->BuildMruWindowList(kActiveDesk)) {
Ahmed Fakhry7664e912019-06-19 16:56:26357 if (!base::Contains(windows_, window))
Ahmed Fakhry00f778472019-05-08 00:50:09358 continue;
359
360 // Do not activate minimized windows, otherwise they will unminimize.
James Cook00e65e92019-07-25 03:19:08361 if (WindowState::Get(window)->IsMinimized())
Ahmed Fakhry00f778472019-05-08 00:50:09362 continue;
363
364 wm::ActivateWindow(window);
365 return;
Ahmed Fakhryeb7375b22019-04-24 22:47:36366 }
367}
368
Ahmed Fakhryce8bf1d822019-04-30 22:06:24369void Desk::Deactivate(bool update_window_activation) {
Ahmed Fakhry2bc12682020-02-08 00:20:11370 DCHECK(is_active_);
371
James Cook00e65e92019-07-25 03:19:08372 auto* active_window = window_util::GetActiveWindow();
Ahmed Fakhryeb7375b22019-04-24 22:47:36373
374 // Hide the associated containers on all roots.
375 for (aura::Window* root : Shell::GetAllRootWindows())
376 root->GetChildById(container_id_)->Hide();
377
Ahmed Fakhryce8bf1d822019-04-30 22:06:24378 is_active_ = false;
chinsenj932efe342021-03-06 06:17:14379 last_day_visited_ = GetDaysFromLocalEpoch();
Ahmed Fakhryce8bf1d822019-04-30 22:06:24380
381 if (!update_window_activation)
382 return;
383
384 // Deactivate the active window (if it belongs to this desk; active window may
385 // be on a different container, or one of the widgets created by overview mode
386 // which are not considered desk windows) after this desk's associated
Ahmed Fakhryeb7375b22019-04-24 22:47:36387 // containers have been hidden. This is to prevent the focus controller from
388 // activating another window on the same desk when the active window loses
389 // focus.
Ahmed Fakhry7664e912019-06-19 16:56:26390 if (active_window && base::Contains(windows_, active_window))
Ahmed Fakhryeb7375b22019-04-24 22:47:36391 wm::DeactivateWindow(active_window);
Ahmed Fakhryeb7375b22019-04-24 22:47:36392}
393
394void Desk::MoveWindowsToDesk(Desk* target_desk) {
395 DCHECK(target_desk);
396
Ahmed Fakhry927eabc2019-05-11 00:14:15397 {
Ahmed Fakhry6d5990922019-07-10 01:16:50398 ScopedWindowPositionerDisabler window_positioner_disabler;
399
Ahmed Fakhry927eabc2019-05-11 00:14:15400 // Throttle notifying the observers, while we move those windows and notify
401 // them only once when done.
Ahmed Fakhrybab46dd2019-05-23 00:27:34402 auto this_desk_throttled = GetScopedNotifyContentChangedDisabler();
403 auto target_desk_throttled =
404 target_desk->GetScopedNotifyContentChangedDisabler();
Ahmed Fakhryeb7375b22019-04-24 22:47:36405
Ahmed Fakhry7664e912019-06-19 16:56:26406 // Moving windows will change the hierarchy and hence |windows_|, and has to
407 // be done without changing the relative z-order. So we make a copy of all
Ahmed Fakhry3d28cb42020-02-27 21:37:04408 // the top-level windows on all the containers of this desk, such that
409 // windows in each container are copied from top-most (z-order) to
410 // bottom-most.
411 // Note that moving windows out of the container and restacking them
412 // differently may trigger events that lead to destroying a window on the
413 // list. For example moving the top-most window which has a backdrop will
414 // cause the backdrop to be destroyed. Therefore observe such events using
415 // an |aura::WindowTracker|.
416 aura::WindowTracker windows_to_move;
Ahmed Fakhry7664e912019-06-19 16:56:26417 for (aura::Window* root : Shell::GetAllRootWindows()) {
418 const aura::Window* container = GetDeskContainerForRoot(root);
Ahmed Fakhry3d28cb42020-02-27 21:37:04419 for (auto* window : base::Reversed(container->children()))
420 windows_to_move.Add(window);
Ahmed Fakhry7664e912019-06-19 16:56:26421 }
Ahmed Fakhry927eabc2019-05-11 00:14:15422
Ahmed Fakhry3d28cb42020-02-27 21:37:04423 auto* mru_tracker = Shell::Get()->mru_window_tracker();
424 while (!windows_to_move.windows().empty()) {
425 auto* window = windows_to_move.Pop();
426 if (!CanMoveWindowOutOfDeskContainer(window))
427 continue;
428
429 // Note that windows that belong to the same container in
430 // |windows_to_move| are sorted from top-most to bottom-most, hence
431 // calling |StackChildAtBottom()| on each in this order will maintain that
432 // same order in the |target_desk|'s container.
Ahmed Fakhryd984db512020-03-13 22:02:24433 MoveWindowToDeskInternal(window, target_desk, window->GetRootWindow());
Ahmed Fakhry3d28cb42020-02-27 21:37:04434 window->parent()->StackChildAtBottom(window);
435 mru_tracker->OnWindowMovedOutFromRemovingDesk(window);
Ahmed Fakhry756d77a2019-05-31 23:21:23436 }
Ahmed Fakhryeb7375b22019-04-24 22:47:36437 }
438
Ahmed3892c172020-03-24 17:54:36439 NotifyContentChanged();
440 target_desk->NotifyContentChanged();
Ahmed Fakhryeb7375b22019-04-24 22:47:36441}
442
Ahmed Fakhryd984db512020-03-13 22:02:24443void Desk::MoveWindowToDesk(aura::Window* window,
444 Desk* target_desk,
445 aura::Window* target_root) {
Ahmed Fakhrybab46dd2019-05-23 00:27:34446 DCHECK(window);
Ahmed Fakhryd984db512020-03-13 22:02:24447 DCHECK(target_desk);
448 DCHECK(target_root);
Ahmed Fakhry7664e912019-06-19 16:56:26449 DCHECK(base::Contains(windows_, window));
Ahmed Fakhrybab46dd2019-05-23 00:27:34450 DCHECK(this != target_desk);
Ahmed Fakhrybaac2b312020-02-19 21:21:55451 // The desks bar should not be allowed to move individually to another desk.
452 // Only as part of `MoveWindowsToDesk()` when the desk is removed.
453 DCHECK_NE(window->id(), kShellWindowId_DesksBarWindow);
Ahmed Fakhrybab46dd2019-05-23 00:27:34454
455 {
Ahmed Fakhry6d5990922019-07-10 01:16:50456 ScopedWindowPositionerDisabler window_positioner_disabler;
457
Ahmed Fakhry7664e912019-06-19 16:56:26458 // Throttling here is necessary even though we're attempting to move a
459 // single window. This is because that window might exist in a transient
460 // window tree, which will result in actually moving multiple windows if the
461 // transient children used to be on the same container.
462 // See `wm::TransientWindowManager::OnWindowHierarchyChanged()`.
Ahmed Fakhrybab46dd2019-05-23 00:27:34463 auto this_desk_throttled = GetScopedNotifyContentChangedDisabler();
464 auto target_desk_throttled =
465 target_desk->GetScopedNotifyContentChangedDisabler();
466
Ahmed Fakhry7664e912019-06-19 16:56:26467 // Always move the root of the transient window tree. We should never move a
468 // transient child and leave its parent behind. Moving the transient
469 // descendants that exist on the same desk container will be taken care of
470 // by `wm::TransientWindowManager::OnWindowHierarchyChanged()`.
471 aura::Window* transient_root = ::wm::GetTransientRoot(window);
Ahmed Fakhryd984db512020-03-13 22:02:24472 MoveWindowToDeskInternal(transient_root, target_desk, target_root);
Ahmed Fakhryfc2ca392021-01-26 17:59:25473 FixWindowStackingAccordingToGlobalMru(transient_root);
Ahmed Fakhrye61d2af2019-06-06 01:29:15474
475 // Unminimize the window so that it shows up in the mini_view after it had
chinsenj12a1104d2021-01-08 00:30:28476 // been dragged and moved to another desk. Don't unminimize if the window is
477 // visible on all desks since it's being moved during desk activation.
James Cook00e65e92019-07-25 03:19:08478 auto* window_state = WindowState::Get(transient_root);
chinsenj12a1104d2021-01-08 00:30:28479 if (window_state->IsMinimized() &&
480 !window->GetProperty(aura::client::kVisibleOnAllWorkspacesKey)) {
Ahmed Fakhrye61d2af2019-06-06 01:29:15481 window_state->Unminimize();
chinsenj12a1104d2021-01-08 00:30:28482 }
Ahmed Fakhrybab46dd2019-05-23 00:27:34483 }
484
Ahmed3892c172020-03-24 17:54:36485 NotifyContentChanged();
486 target_desk->NotifyContentChanged();
Ahmed Fakhrybab46dd2019-05-23 00:27:34487}
488
Ahmed Fakhryeb7375b22019-04-24 22:47:36489aura::Window* Desk::GetDeskContainerForRoot(aura::Window* root) const {
490 DCHECK(root);
491
492 return root->GetChildById(container_id_);
493}
494
Ahmed3892c172020-03-24 17:54:36495void Desk::NotifyContentChanged() {
Ahmed Fakhry9df7b432019-05-15 17:02:24496 if (!should_notify_content_changed_)
Ahmed Fakhry927eabc2019-05-11 00:14:15497 return;
498
Ahmed3892c172020-03-24 17:54:36499 // Updating the backdrops below may lead to the removal or creation of
500 // backdrop windows in this desk, which can cause us to recurse back here.
501 // Disable this.
502 auto disable_recursion = GetScopedNotifyContentChangedDisabler();
503
504 // The availability and visibility of backdrops of all containers associated
505 // with this desk will be updated *before* notifying observer, so that the
506 // mini_views update *after* the backdrops do.
507 // This is *only* needed if the WorkspaceLayoutManager won't take care of this
508 // for us while overview is active.
509 if (Shell::Get()->overview_controller()->InOverviewSession())
Ahmed Fakhryed257cf2020-01-10 03:25:03510 UpdateDeskBackdrops();
Ahmed Fakhry756d77a2019-05-31 23:21:23511
Ahmed Fakhry927eabc2019-05-11 00:14:15512 for (auto& observer : observers_)
Ahmed Fakhry9df7b432019-05-15 17:02:24513 observer.OnContentChanged();
Ahmed Fakhryeb7375b22019-04-24 22:47:36514}
Ahmed Fakhryec3483792019-03-04 18:09:27515
Ahmed Fakhry756d77a2019-05-31 23:21:23516void Desk::UpdateDeskBackdrops() {
517 for (auto* root : Shell::GetAllRootWindows())
518 UpdateBackdropController(GetDeskContainerForRoot(root));
519}
520
Cattalyya Nuengsigkapian8c45e302021-01-06 21:48:08521void Desk::SetDeskBeingRemoved() {
522 is_desk_being_removed_ = true;
523}
524
chinsenj7936a652021-03-04 18:26:26525void Desk::RecordLifetimeHistogram() {
526 // Desk index is 1-indexed in histograms.
527 const int desk_index =
528 Shell::Get()->desks_controller()->GetDeskIndex(this) + 1;
529 base::UmaHistogramCounts1000(
530 base::StringPrintf("%s%i", kDeskLifetimeHistogramNamePrefix, desk_index),
531 (base::Time::Now() - creation_time_).InHours());
532}
533
chinsenj932efe342021-03-06 06:17:14534bool Desk::IsConsecutiveDailyVisit() const {
535 if (last_day_visited_ == -1)
536 return true;
537
538 const int days_since_last_visit = GetDaysFromLocalEpoch() - last_day_visited_;
539 return days_since_last_visit <= 1;
540}
541
542void Desk::RecordAndResetConsecutiveDailyVisits(bool being_removed) {
543 if (being_removed && is_active_) {
544 // When the user removes the active desk, update |last_day_visited_| to the
545 // current day to account for the time they spent on this desk.
546 last_day_visited_ = GetDaysFromLocalEpoch();
547 }
548
549 const int consecutive_daily_visits =
550 last_day_visited_ - first_day_visited_ + 1;
551 DCHECK_GE(consecutive_daily_visits, 1);
552 base::UmaHistogramCounts1000(kConsecutiveDailyVisitsHistogramName,
553 consecutive_daily_visits);
554
555 last_day_visited_ = -1;
556 first_day_visited_ = -1;
557}
558
559int Desk::GetDaysFromLocalEpoch() const {
560 base::Time now = override_clock_ ? override_clock_->Now() : base::Time::Now();
561 return (now - GetLocalEpoch()).InDays();
562}
563
564void Desk::OverrideClockForTesting(base::Clock* test_clock) {
565 DCHECK(!override_clock_);
566 override_clock_ = test_clock;
567}
568
569void Desk::ResetVisitedMetricsForTesting() {
570 const int current_date = GetDaysFromLocalEpoch();
571 first_day_visited_ = current_date;
572 last_day_visited_ = current_date;
573}
574
Ahmed Fakhryd984db512020-03-13 22:02:24575void Desk::MoveWindowToDeskInternal(aura::Window* window,
576 Desk* target_desk,
577 aura::Window* target_root) {
Ahmed Fakhry7664e912019-06-19 16:56:26578 DCHECK(base::Contains(windows_, window));
Ahmed Fakhry756d77a2019-05-31 23:21:23579 DCHECK(CanMoveWindowOutOfDeskContainer(window))
580 << "Non-desk windows are not allowed to move out of the container.";
581
Ahmed Fakhryd984db512020-03-13 22:02:24582 // When |target_root| is different than the current window's |root|, this can
583 // only happen when dragging and dropping a window on mini desk view on
584 // another display. Therefore |target_desk| is an inactive desk (i.e.
585 // invisible). The order doesn't really matter, but we move the window to the
586 // target desk's container first (so that it becomes hidden), then move it to
587 // the target display (while it's hidden).
Ahmed Fakhrybab46dd2019-05-23 00:27:34588 aura::Window* root = window->GetRootWindow();
589 aura::Window* source_container = GetDeskContainerForRoot(root);
590 aura::Window* target_container = target_desk->GetDeskContainerForRoot(root);
Ahmed Fakhry7664e912019-06-19 16:56:26591 DCHECK(window->parent() == source_container);
592 target_container->AddChild(window);
Ahmed Fakhryd984db512020-03-13 22:02:24593
594 if (root != target_root) {
595 // Move the window to the container with the same ID on the target display's
596 // root (i.e. container that belongs to the same desk), and adjust its
597 // bounds to fit in the new display's work area.
598 window_util::MoveWindowToDisplay(window,
599 display::Screen::GetScreen()
600 ->GetDisplayNearestWindow(target_root)
601 .id());
602 DCHECK_EQ(target_desk->container_id_, window->parent()->id());
603 }
Ahmed Fakhrybab46dd2019-05-23 00:27:34604}
605
Ahmed Fakhry2bc12682020-02-08 00:20:11606bool Desk::MaybeResetContainersOpacities() {
607 if (!started_activation_animation_)
608 return false;
609
610 for (aura::Window* root : Shell::GetAllRootWindows()) {
611 auto* container = root->GetChildById(container_id_);
612 container->layer()->SetOpacity(1);
613 }
614 started_activation_animation_ = false;
615 return true;
616}
617
Ahmed Fakhryec3483792019-03-04 18:09:27618} // namespace ash