blob: 4f68cb7da1eadc93f5937c20ab39724f730a6906 [file] [log] [blame]
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "ash/wm/desks/desks_controller.h"
#include <utility>
#include "ash/accessibility/accessibility_controller_impl.h"
#include "ash/public/cpp/ash_features.h"
#include "ash/public/cpp/shelf_model.h"
#include "ash/public/cpp/shelf_types.h"
#include "ash/public/cpp/shell_window_ids.h"
#include "ash/public/cpp/window_properties.h"
#include "ash/session/session_controller_impl.h"
#include "ash/shell.h"
#include "ash/shell_delegate.h"
#include "ash/strings/grit/ash_strings.h"
#include "ash/wm/desks/desk.h"
#include "ash/wm/desks/desk_animation_base.h"
#include "ash/wm/desks/desk_animation_impl.h"
#include "ash/wm/desks/desks_animations.h"
#include "ash/wm/desks/desks_restore_util.h"
#include "ash/wm/desks/desks_util.h"
#include "ash/wm/mru_window_tracker.h"
#include "ash/wm/overview/overview_controller.h"
#include "ash/wm/overview/overview_grid.h"
#include "ash/wm/overview/overview_item.h"
#include "ash/wm/splitview/split_view_controller.h"
#include "ash/wm/splitview/split_view_utils.h"
#include "ash/wm/switchable_windows.h"
#include "ash/wm/window_cycle_controller.h"
#include "ash/wm/window_util.h"
#include "base/auto_reset.h"
#include "base/check_op.h"
#include "base/containers/contains.h"
#include "base/containers/unique_ptr_adapters.h"
#include "base/metrics/histogram_functions.h"
#include "base/metrics/histogram_macros.h"
#include "base/notreached.h"
#include "base/numerics/ranges.h"
#include "base/timer/timer.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/wm/public/activation_client.h"
namespace ash {
namespace {
constexpr char kNewDeskHistogramName[] = "Ash.Desks.NewDesk2";
constexpr char kDesksCountHistogramName[] = "Ash.Desks.DesksCount2";
constexpr char kRemoveDeskHistogramName[] = "Ash.Desks.RemoveDesk";
constexpr char kDeskSwitchHistogramName[] = "Ash.Desks.DesksSwitch";
constexpr char kMoveWindowFromActiveDeskHistogramName[] =
"Ash.Desks.MoveWindowFromActiveDesk";
constexpr char kNumberOfWindowsOnDesk_1_HistogramName[] =
"Ash.Desks.NumberOfWindowsOnDesk_1";
constexpr char kNumberOfWindowsOnDesk_2_HistogramName[] =
"Ash.Desks.NumberOfWindowsOnDesk_2";
constexpr char kNumberOfWindowsOnDesk_3_HistogramName[] =
"Ash.Desks.NumberOfWindowsOnDesk_3";
constexpr char kNumberOfWindowsOnDesk_4_HistogramName[] =
"Ash.Desks.NumberOfWindowsOnDesk_4";
constexpr char kNumberOfWindowsOnDesk_5_HistogramName[] =
"Ash.Desks.NumberOfWindowsOnDesk_5";
constexpr char kNumberOfWindowsOnDesk_6_HistogramName[] =
"Ash.Desks.NumberOfWindowsOnDesk_6";
constexpr char kNumberOfWindowsOnDesk_7_HistogramName[] =
"Ash.Desks.NumberOfWindowsOnDesk_7";
constexpr char kNumberOfWindowsOnDesk_8_HistogramName[] =
"Ash.Desks.NumberOfWindowsOnDesk_8";
constexpr char kNumberOfDeskTraversalsHistogramName[] =
"Ash.Desks.NumberOfDeskTraversals";
constexpr int kDeskTraversalsMaxValue = 20;
// After an desk activation animation starts,
// |kNumberOfDeskTraversalsHistogramName| will be recorded after this time
// interval.
constexpr base::TimeDelta kDeskTraversalsTimeout =
base::TimeDelta::FromSeconds(5);
constexpr int kDeskDefaultNameIds[] = {
IDS_ASH_DESKS_DESK_1_MINI_VIEW_TITLE, IDS_ASH_DESKS_DESK_2_MINI_VIEW_TITLE,
IDS_ASH_DESKS_DESK_3_MINI_VIEW_TITLE, IDS_ASH_DESKS_DESK_4_MINI_VIEW_TITLE,
IDS_ASH_DESKS_DESK_5_MINI_VIEW_TITLE, IDS_ASH_DESKS_DESK_6_MINI_VIEW_TITLE,
IDS_ASH_DESKS_DESK_7_MINI_VIEW_TITLE, IDS_ASH_DESKS_DESK_8_MINI_VIEW_TITLE};
// Appends the given |windows| to the end of the currently active overview mode
// session such that the most-recently used window is added first. If
// The windows will animate to their positions in the overview grid.
void AppendWindowsToOverview(const std::vector<aura::Window*>& windows) {
DCHECK(Shell::Get()->overview_controller()->InOverviewSession());
auto* overview_session =
Shell::Get()->overview_controller()->overview_session();
for (auto* window :
Shell::Get()->mru_window_tracker()->BuildMruWindowList(kActiveDesk)) {
if (!base::Contains(windows, window) ||
window_util::ShouldExcludeForOverview(window)) {
continue;
}
overview_session->AppendItem(window, /*reposition=*/true, /*animate=*/true);
}
}
// Removes all the items that currently exist in overview.
void RemoveAllWindowsFromOverview() {
DCHECK(Shell::Get()->overview_controller()->InOverviewSession());
auto* overview_session =
Shell::Get()->overview_controller()->overview_session();
for (const auto& grid : overview_session->grid_list()) {
while (!grid->empty())
overview_session->RemoveItem(grid->window_list()[0].get());
}
}
// Updates the |ShelfItem::is_on_active_desk| of the items associated with
// |windows_on_inactive_desk| and |windows_on_active_desk|. The items of the
// given windows will be updated, while the rest will remain unchanged. Either
// or both window lists can be empty.
void MaybeUpdateShelfItems(
const std::vector<aura::Window*>& windows_on_inactive_desk,
const std::vector<aura::Window*>& windows_on_active_desk) {
if (!features::IsPerDeskShelfEnabled())
return;
auto* shelf_model = ShelfModel::Get();
DCHECK(shelf_model);
std::vector<ShelfModel::ItemDeskUpdate> shelf_items_updates;
auto add_shelf_item_update = [&](aura::Window* window,
bool is_on_active_desk) {
const ShelfID shelf_id =
ShelfID::Deserialize(window->GetProperty(kShelfIDKey));
const int index = shelf_model->ItemIndexByID(shelf_id);
if (index < 0)
return;
shelf_items_updates.push_back({index, is_on_active_desk});
};
for (auto* window : windows_on_inactive_desk)
add_shelf_item_update(window, /*is_on_active_desk=*/false);
for (auto* window : windows_on_active_desk)
add_shelf_item_update(window, /*is_on_active_desk=*/true);
shelf_model->UpdateItemsForDeskChange(shelf_items_updates);
}
bool IsParentSwitchableContainer(const aura::Window* window) {
DCHECK(window);
return window->parent() && IsSwitchableContainer(window->parent());
}
} // namespace
// Helper class which wraps around a OneShotTimer and used for recording how
// many times the user has traversed desks. Here traversal means the amount of
// times the user has seen a visual desk change. This differs from desk
// activation as a desk is only activated as needed for a screenshot during an
// animation. The user may bounce back and forth on two desks that already
// have screenshots, and each bounce is recorded as a traversal. For touchpad
// swipes, the amount of traversals in one animation depends on the amount of
// changes in the most visible desk have been seen. For other desk changes,
// the amount of traversals in one animation is 1 + number of Replace() calls.
// Multiple animations may be recorded before the timer stops.
class DesksController::DeskTraversalsMetricsHelper {
public:
explicit DeskTraversalsMetricsHelper(DesksController* controller)
: controller_(controller) {}
DeskTraversalsMetricsHelper(const DeskTraversalsMetricsHelper&) = delete;
DeskTraversalsMetricsHelper& operator=(const DeskTraversalsMetricsHelper&) =
delete;
~DeskTraversalsMetricsHelper() = default;
// Starts |timer_| unless it is already running.
void MaybeStart() {
if (timer_.IsRunning())
return;
count_ = 0;
timer_.Start(FROM_HERE, kDeskTraversalsTimeout,
base::BindOnce(&DeskTraversalsMetricsHelper::OnTimerStop,
base::Unretained(this)));
}
// Called when a desk animation is finished. Adds all observed
// |visible_desk_changes| to |count_|.
void OnAnimationFinished(int visible_desk_changes) {
if (timer_.IsRunning())
count_ += visible_desk_changes;
}
private:
void OnTimerStop() {
// If an animation is still running, add its current visible desk change
// count to |count_|.
DeskAnimationBase* current_animation = controller_->animation();
if (current_animation)
count_ += current_animation->visible_desk_changes();
base::UmaHistogramExactLinear(kNumberOfDeskTraversalsHistogramName, count_,
kDeskTraversalsMaxValue);
}
// Pointer to the DesksController that owns this. Guaranteed to be not
// nullptr for the lifetime of |this|.
DesksController* const controller_;
base::OneShotTimer timer_;
// Tracks the amount of traversals that have happened since |timer_| has
// started.
int count_ = 0;
};
DesksController::DesksController()
: is_enhanced_desk_animations_(features::IsEnhancedDeskAnimations()),
metrics_helper_(std::make_unique<DeskTraversalsMetricsHelper>(this)) {
Shell::Get()->activation_client()->AddObserver(this);
Shell::Get()->session_controller()->AddObserver(this);
for (int id : desks_util::GetDesksContainersIds())
available_container_ids_.push(id);
// There's always one default desk. The DesksCreationRemovalSource used here
// doesn't matter, since UMA reporting will be skipped for the first ever
// default desk.
NewDesk(DesksCreationRemovalSource::kButton);
active_desk_ = desks_.back().get();
active_desk_->Activate(/*update_window_activation=*/true);
}
DesksController::~DesksController() {
Shell::Get()->session_controller()->RemoveObserver(this);
Shell::Get()->activation_client()->RemoveObserver(this);
}
// static
DesksController* DesksController::Get() {
return Shell::Get()->desks_controller();
}
// static
base::string16 DesksController::GetDeskDefaultName(size_t desk_index) {
DCHECK_LT(desk_index, desks_util::GetMaxNumberOfDesks());
return l10n_util::GetStringUTF16(kDeskDefaultNameIds[desk_index]);
}
const Desk* DesksController::GetTargetActiveDesk() const {
if (animation_)
return desks_[animation_->ending_desk_index()].get();
return active_desk();
}
void DesksController::RestorePrimaryUserActiveDeskIndex(int active_desk_index) {
DCHECK_GE(active_desk_index, 0);
DCHECK_LT(active_desk_index, int{desks_.size()});
user_to_active_desk_index_[Shell::Get()
->session_controller()
->GetPrimaryUserSession()
->user_info.account_id] = active_desk_index;
// Following |OnActiveUserSessionChanged| approach, restoring uses
// DesksSwitchSource::kUserSwitch as a desk switch source.
// TODO(crbug.com/1145404): consider adding an UMA metric for desks
// restoring to change the source to kDeskRestored.
ActivateDesk(desks_[active_desk_index].get(), DesksSwitchSource::kUserSwitch);
}
void DesksController::Shutdown() {
animation_.reset();
}
void DesksController::AddObserver(Observer* observer) {
observers_.AddObserver(observer);
}
void DesksController::RemoveObserver(Observer* observer) {
observers_.RemoveObserver(observer);
}
bool DesksController::AreDesksBeingModified() const {
return are_desks_being_modified_ || !!animation_;
}
bool DesksController::CanCreateDesks() const {
return desks_.size() < desks_util::GetMaxNumberOfDesks();
}
Desk* DesksController::GetNextDesk() const {
int next_index = GetDeskIndex(GetTargetActiveDesk());
if (++next_index >= static_cast<int>(desks_.size()))
return nullptr;
return desks_[next_index].get();
}
Desk* DesksController::GetPreviousDesk() const {
int previous_index = GetDeskIndex(GetTargetActiveDesk());
if (--previous_index < 0)
return nullptr;
return desks_[previous_index].get();
}
bool DesksController::CanRemoveDesks() const {
return desks_.size() > 1;
}
void DesksController::NewDesk(DesksCreationRemovalSource source) {
DCHECK(CanCreateDesks());
DCHECK(!available_container_ids_.empty());
base::AutoReset<bool> in_progress(&are_desks_being_modified_, true);
// The first default desk should not overwrite any desks restore data, nor
// should it trigger any UMA stats reports.
const bool is_first_ever_desk = desks_.empty();
desks_.push_back(std::make_unique<Desk>(available_container_ids_.front()));
available_container_ids_.pop();
Desk* new_desk = desks_.back().get();
// If Bento is enabled and the user creates a desk with the button, the new
// desk should have an empty name to encourage them to rename their desks.
const bool empty_name = features::IsBentoEnabled() &&
source == DesksCreationRemovalSource::kButton &&
desks_.size() > 1;
if (!empty_name) {
new_desk->SetName(GetDeskDefaultName(desks_.size() - 1),
/*set_by_user=*/false);
}
auto* shell = Shell::Get();
shell->accessibility_controller()->TriggerAccessibilityAlertWithMessage(
l10n_util::GetStringFUTF8(IDS_ASH_VIRTUAL_DESKS_ALERT_NEW_DESK_CREATED,
base::NumberToString16(desks_.size())));
for (auto& observer : observers_)
observer.OnDeskAdded(new_desk);
shell->shell_delegate()->DesksStateChanged(desks_.size());
if (!is_first_ever_desk) {
desks_restore_util::UpdatePrimaryUserDesksPrefs();
UMA_HISTOGRAM_ENUMERATION(kNewDeskHistogramName, source);
ReportDesksCountHistogram();
}
}
void DesksController::RemoveDesk(const Desk* desk,
DesksCreationRemovalSource source) {
DCHECK(CanRemoveDesks());
DCHECK(HasDesk(desk));
base::AutoReset<bool> in_progress(&are_desks_being_modified_, true);
auto* overview_controller = Shell::Get()->overview_controller();
const bool in_overview = overview_controller->InOverviewSession();
if (!in_overview && active_desk_ == desk) {
// When removing the active desk outside of overview, we trigger the remove
// desk animation. We will activate the desk to its left if any, otherwise,
// we activate one on the right.
const int current_desk_index = GetDeskIndex(active_desk_);
const int target_desk_index =
current_desk_index + ((current_desk_index > 0) ? -1 : 1);
DCHECK_GE(target_desk_index, 0);
DCHECK_LT(target_desk_index, static_cast<int>(desks_.size()));
animation_ = std::make_unique<DeskRemovalAnimation>(
this, current_desk_index, target_desk_index, source);
animation_->Launch();
return;
}
RemoveDeskInternal(desk, source);
}
void DesksController::ActivateDesk(const Desk* desk, DesksSwitchSource source) {
DCHECK(HasDesk(desk));
DCHECK(!animation_);
OverviewController* overview_controller = Shell::Get()->overview_controller();
const bool in_overview = overview_controller->InOverviewSession();
if (desk == active_desk_) {
if (in_overview) {
// Selecting the active desk's mini_view in overview mode is allowed and
// should just exit overview mode normally.
overview_controller->EndOverview();
}
return;
}
UMA_HISTOGRAM_ENUMERATION(kDeskSwitchHistogramName, source);
const int target_desk_index = GetDeskIndex(desk);
if (source != DesksSwitchSource::kDeskRemoved) {
// Desk removal has its own a11y alert.
Shell::Get()
->accessibility_controller()
->TriggerAccessibilityAlertWithMessage(l10n_util::GetStringFUTF8(
IDS_ASH_VIRTUAL_DESKS_ALERT_DESK_ACTIVATED, desk->name()));
}
if (source == DesksSwitchSource::kDeskRemoved ||
source == DesksSwitchSource::kUserSwitch) {
// Desk switches due to desks removal or user switches in a multi-profile
// session result in immediate desk activation without animation.
ActivateDeskInternal(desk, /*update_window_activation=*/!in_overview);
return;
}
// When switching desks we want to update window activation when leaving
// overview or if nothing was active prior to switching desks. This will
// ensure that after switching desks, we will try to focus a candidate window.
// We will also update window activation if the currently active window is one
// in a switchable container. Otherwise, do not update the window activation.
// This will prevent some system UI windows like the app list from closing
// when switching desks.
aura::Window* active_window = window_util::GetActiveWindow();
const bool update_window_activation =
in_overview || !active_window ||
IsParentSwitchableContainer(active_window);
const int starting_desk_index = GetDeskIndex(active_desk());
animation_ = std::make_unique<DeskActivationAnimation>(
this, starting_desk_index, target_desk_index, source,
update_window_activation);
animation_->Launch();
metrics_helper_->MaybeStart();
}
bool DesksController::ActivateAdjacentDesk(bool going_left,
DesksSwitchSource source) {
// An on-going desk switch animation might be in progress. Skip this
// accelerator or touchpad event if enhanced desk animations are not enabled.
if (!is_enhanced_desk_animations_ && AreDesksBeingModified())
return false;
if (Shell::Get()->session_controller()->IsUserSessionBlocked())
return false;
// Try replacing an ongoing desk animation of the same source.
if (is_enhanced_desk_animations_ && animation_ &&
animation_->Replace(going_left, source)) {
return true;
}
const Desk* desk_to_activate = going_left ? GetPreviousDesk() : GetNextDesk();
if (desk_to_activate) {
ActivateDesk(desk_to_activate, source);
} else {
for (auto* root : Shell::GetAllRootWindows())
desks_animations::PerformHitTheWallAnimation(root, going_left);
}
return true;
}
bool DesksController::StartSwipeAnimation(bool move_left) {
DCHECK(is_enhanced_desk_animations_);
// Activate an adjacent desk. It will replace an ongoing touchpad animation if
// one exists.
return ActivateAdjacentDesk(move_left,
DesksSwitchSource::kDeskSwitchTouchpad);
}
void DesksController::UpdateSwipeAnimation(float scroll_delta_x) {
DCHECK(is_enhanced_desk_animations_);
if (animation_)
animation_->UpdateSwipeAnimation(scroll_delta_x);
}
void DesksController::EndSwipeAnimation() {
DCHECK(is_enhanced_desk_animations_);
if (animation_)
animation_->EndSwipeAnimation();
}
bool DesksController::MoveWindowFromActiveDeskTo(
aura::Window* window,
Desk* target_desk,
aura::Window* target_root,
DesksMoveWindowFromActiveDeskSource source) {
DCHECK_NE(active_desk_, target_desk);
// An active window might be an always-on-top or pip which doesn't belong to
// the active desk, and hence cannot be removed.
if (!base::Contains(active_desk_->windows(), window))
return false;
base::AutoReset<bool> in_progress(&are_desks_being_modified_, true);
auto* overview_controller = Shell::Get()->overview_controller();
const bool in_overview = overview_controller->InOverviewSession();
// The below order matters:
// If in overview, remove the item from overview first, before calling
// MoveWindowToDesk(), since MoveWindowToDesk() unminimizes the window (if it
// was minimized) before updating the mini views. We shouldn't change the
// window's minimized state before removing it from overview, since overview
// handles minimized windows differently.
if (in_overview) {
auto* overview_session = overview_controller->overview_session();
auto* item = overview_session->GetOverviewItemForWindow(window);
DCHECK(item);
item->OnMovingWindowToAnotherDesk();
// The item no longer needs to be in the overview grid.
overview_session->RemoveItem(item);
}
active_desk_->MoveWindowToDesk(window, target_desk, target_root);
MaybeUpdateShelfItems(/*windows_on_inactive_desk=*/{window},
/*windows_on_active_desk=*/{});
Shell::Get()
->accessibility_controller()
->TriggerAccessibilityAlertWithMessage(l10n_util::GetStringFUTF8(
IDS_ASH_VIRTUAL_DESKS_ALERT_WINDOW_MOVED_FROM_ACTIVE_DESK,
window->GetTitle(), active_desk_->name(), target_desk->name()));
UMA_HISTOGRAM_ENUMERATION(kMoveWindowFromActiveDeskHistogramName, source);
ReportNumberOfWindowsPerDeskHistogram();
// A window moving out of the active desk cannot be active.
// If we are in overview, we should not change the window activation as we do
// below, since the dummy "OverviewModeFocusedWidget" should remain active
// while overview mode is active.
if (!in_overview)
wm::DeactivateWindow(window);
return true;
}
void DesksController::RevertDeskNameToDefault(Desk* desk) {
DCHECK(HasDesk(desk));
desk->SetName(GetDeskDefaultName(GetDeskIndex(desk)), /*set_by_user=*/false);
}
void DesksController::RestoreNameOfDeskAtIndex(base::string16 name,
size_t index) {
DCHECK(!name.empty());
DCHECK_LT(index, desks_.size());
desks_[index]->SetName(std::move(name), /*set_by_user=*/true);
}
void DesksController::OnRootWindowAdded(aura::Window* root_window) {
for (auto& desk : desks_)
desk->OnRootWindowAdded(root_window);
}
void DesksController::OnRootWindowClosing(aura::Window* root_window) {
for (auto& desk : desks_)
desk->OnRootWindowClosing(root_window);
}
int DesksController::GetDeskIndex(const Desk* desk) const {
for (size_t i = 0; i < desks_.size(); ++i) {
if (desk == desks_[i].get())
return i;
}
NOTREACHED();
return -1;
}
bool DesksController::BelongsToActiveDesk(aura::Window* window) {
return desks_util::BelongsToActiveDesk(window);
}
int DesksController::GetActiveDeskIndex() const {
return GetDeskIndex(active_desk_);
}
base::string16 DesksController::GetDeskName(int index) const {
return index < static_cast<int>(desks_.size()) ? desks_[index]->name()
: base::string16();
}
int DesksController::GetNumberOfDesks() const {
return static_cast<int>(desks_.size());
}
void DesksController::SendToDeskAtIndex(aura::Window* window, int desk_index) {
if (desk_index < 0 || desk_index >= static_cast<int>(desks_.size()))
return;
const int active_desk_index = GetDeskIndex(active_desk_);
if (desk_index == active_desk_index)
return;
DCHECK(desks_.at(desk_index));
desks_animations::PerformWindowMoveToDeskAnimation(
window, /*going_left=*/desk_index < active_desk_index);
MoveWindowFromActiveDeskTo(window, desks_[desk_index].get(),
window->GetRootWindow(),
DesksMoveWindowFromActiveDeskSource::kSendToDesk);
}
void DesksController::OnWindowActivating(ActivationReason reason,
aura::Window* gaining_active,
aura::Window* losing_active) {
if (AreDesksBeingModified())
return;
if (!gaining_active)
return;
const Desk* window_desk = FindDeskOfWindow(gaining_active);
if (!window_desk || window_desk == active_desk_)
return;
ActivateDesk(window_desk, DesksSwitchSource::kWindowActivated);
}
void DesksController::OnWindowActivated(ActivationReason reason,
aura::Window* gained_active,
aura::Window* lost_active) {}
void DesksController::OnActiveUserSessionChanged(const AccountId& account_id) {
// TODO(afakhry): Remove this when multi-profile support goes away.
DCHECK(current_account_id_.is_valid());
if (current_account_id_ == account_id) {
return;
}
user_to_active_desk_index_[current_account_id_] = GetDeskIndex(active_desk_);
current_account_id_ = account_id;
// Note the following constraints for secondary users:
// - Simultaneously logged-in users share the same number of desks.
// - We don't sync and restore the number of desks nor the active desk
// position from previous login sessions.
//
// Given the above, we do the following for simplicity:
// - If this user has never been seen before, we activate their first desk.
// - If one of the simultaneously logged-in users remove desks, that other
// users' active-desk indices may become invalid. We won't create extra
// desks for this user, but rather we will simply activate their last desk
// on the right. Future user switches will update the pref for this user to
// the correct value.
int new_user_active_desk_index =
/* This is a default initialized index to 0 if the id doesn't exist. */
user_to_active_desk_index_[current_account_id_];
new_user_active_desk_index = base::ClampToRange(
new_user_active_desk_index, 0, static_cast<int>(desks_.size()) - 1);
ActivateDesk(desks_[new_user_active_desk_index].get(),
DesksSwitchSource::kUserSwitch);
}
void DesksController::OnFirstSessionStarted() {
current_account_id_ =
Shell::Get()->session_controller()->GetActiveAccountId();
desks_restore_util::RestorePrimaryUserDesks();
}
void DesksController::OnAnimationFinished(DeskAnimationBase* animation) {
DCHECK_EQ(animation_.get(), animation);
metrics_helper_->OnAnimationFinished(animation->visible_desk_changes());
animation_.reset();
}
bool DesksController::HasDesk(const Desk* desk) const {
auto iter = std::find_if(
desks_.begin(), desks_.end(),
[desk](const std::unique_ptr<Desk>& d) { return d.get() == desk; });
return iter != desks_.end();
}
void DesksController::ActivateDeskInternal(const Desk* desk,
bool update_window_activation) {
DCHECK(HasDesk(desk));
if (desk == active_desk_)
return;
base::AutoReset<bool> in_progress(&are_desks_being_modified_, true);
// Mark the new desk as active first, so that deactivating windows on the
// `old_active` desk do not activate other windows on the same desk. See
// `ash::AshFocusRules::GetNextActivatableWindow()`.
Desk* old_active = active_desk_;
active_desk_ = const_cast<Desk*>(desk);
// There should always be an active desk at any time.
DCHECK(old_active);
old_active->Deactivate(update_window_activation);
active_desk_->Activate(update_window_activation);
MaybeUpdateShelfItems(old_active->windows(), active_desk_->windows());
// If in the middle of a window cycle gesture, reset the window cycle list
// contents so it contains the new active desk's windows.
auto* shell = Shell::Get();
if (features::IsAltTabLimitedToActiveDesk()) {
auto* window_cycle_controller = shell->window_cycle_controller();
window_cycle_controller->MaybeResetCycleList();
}
for (auto& observer : observers_)
observer.OnDeskActivationChanged(active_desk_, old_active);
// Only update active desk prefs when a primary user switches a desk.
if (features::IsDesksRestoreEnabled() &&
shell->session_controller()->IsUserPrimary()) {
desks_restore_util::UpdatePrimaryUserActiveDeskPrefs(
GetDeskIndex(active_desk_));
}
}
void DesksController::RemoveDeskInternal(const Desk* desk,
DesksCreationRemovalSource source) {
DCHECK(CanRemoveDesks());
base::AutoReset<bool> in_progress(&are_desks_being_modified_, true);
auto iter = std::find_if(
desks_.begin(), desks_.end(),
[desk](const std::unique_ptr<Desk>& d) { return d.get() == desk; });
DCHECK(iter != desks_.end());
// Used by accessibility to indicate the desk that has been removed.
const int removed_desk_number = std::distance(desks_.begin(), iter) + 1;
// Keep the removed desk alive until the end of this function.
std::unique_ptr<Desk> removed_desk = std::move(*iter);
DCHECK_EQ(removed_desk.get(), desk);
auto iter_after = desks_.erase(iter);
DCHECK(!desks_.empty());
auto* shell = Shell::Get();
auto* overview_controller = shell->overview_controller();
const bool in_overview = overview_controller->InOverviewSession();
const std::vector<aura::Window*> removed_desk_windows =
removed_desk->windows();
// No need to spend time refreshing the mini_views of the removed desk.
auto removed_desk_mini_views_pauser =
removed_desk->GetScopedNotifyContentChangedDisabler();
// - Move windows in removed desk (if any) to the currently active desk.
// - If the active desk is the one being removed, activate the desk to its
// left, if no desk to the left, activate one on the right.
const bool will_switch_desks = (removed_desk.get() == active_desk_);
if (!will_switch_desks) {
// We will refresh the mini_views of the active desk only once at the end.
auto active_desk_mini_view_pauser =
active_desk_->GetScopedNotifyContentChangedDisabler();
removed_desk->MoveWindowsToDesk(active_desk_);
MaybeUpdateShelfItems({}, removed_desk_windows);
// If overview mode is active, we add the windows of the removed desk to the
// overview grid in the order of the new MRU (which changes after removing a
// desk by making the windows of the removed desk as the least recently used
// across all desks). Note that this can only be done after the windows have
// moved to the active desk in `MoveWindowsToDesk()` above, so that building
// the window MRU list should contain those windows.
if (in_overview)
AppendWindowsToOverview(removed_desk_windows);
} else {
Desk* target_desk = nullptr;
if (iter_after == desks_.begin()) {
// Nothing before this desk.
target_desk = (*iter_after).get();
} else {
// Back up to select the desk on the left.
target_desk = (*(--iter_after)).get();
}
DCHECK(target_desk);
// The target desk, which is about to become active, will have its
// mini_views refreshed at the end.
auto target_desk_mini_view_pauser =
target_desk->GetScopedNotifyContentChangedDisabler();
// Exit split view if active, before activating the new desk. We will
// restore the split view state of the newly activated desk at the end.
for (aura::Window* root_window : Shell::GetAllRootWindows()) {
SplitViewController::Get(root_window)
->EndSplitView(SplitViewController::EndReason::kDesksChange);
}
// The removed desk is still the active desk, so temporarily remove its
// windows from the overview grid which will result in removing the
// "OverviewModeLabel" widgets created by overview mode for these windows.
// This way the removed desk tracks only real windows, which are now ready
// to be moved to the target desk.
if (in_overview)
RemoveAllWindowsFromOverview();
// If overview mode is active, change desk activation without changing
// window activation. Activation should remain on the dummy
// "OverviewModeFocusedWidget" while overview mode is active.
removed_desk->MoveWindowsToDesk(target_desk);
ActivateDesk(target_desk, DesksSwitchSource::kDeskRemoved);
// Desk activation should not change overview mode state.
DCHECK_EQ(in_overview, overview_controller->InOverviewSession());
// Now that the windows from the removed and target desks merged, add them
// all to the grid in the order of the new MRU.
if (in_overview)
AppendWindowsToOverview(target_desk->windows());
}
// It's OK now to refresh the mini_views of *only* the active desk, and only
// if windows from the removed desk moved to it.
DCHECK(active_desk_->should_notify_content_changed());
if (!removed_desk_windows.empty())
active_desk_->NotifyContentChanged();
UpdateDesksDefaultNames();
for (auto& observer : observers_)
observer.OnDeskRemoved(removed_desk.get());
shell->shell_delegate()->DesksStateChanged(desks_.size());
available_container_ids_.push(removed_desk->container_id());
// Avoid having stale backdrop state as a desk is removed while in overview
// mode, since the backdrop controller won't update the backdrop window as
// the removed desk's windows move out from the container. Therefore, we need
// to update it manually.
if (in_overview)
removed_desk->UpdateDeskBackdrops();
// Restoring split view may start or end overview mode, therefore do this at
// the end to avoid getting into a bad state.
if (will_switch_desks)
MaybeRestoreSplitView(/*refresh_snapped_windows=*/true);
UMA_HISTOGRAM_ENUMERATION(kRemoveDeskHistogramName, source);
ReportDesksCountHistogram();
ReportNumberOfWindowsPerDeskHistogram();
int active_desk_number = GetDeskIndex(active_desk_) + 1;
if (active_desk_number == removed_desk_number)
active_desk_number++;
Shell::Get()
->accessibility_controller()
->TriggerAccessibilityAlertWithMessage(l10n_util::GetStringFUTF8(
IDS_ASH_VIRTUAL_DESKS_ALERT_DESK_REMOVED, removed_desk->name(),
active_desk_->name()));
desks_restore_util::UpdatePrimaryUserDesksPrefs();
DCHECK_LE(available_container_ids_.size(), desks_util::GetMaxNumberOfDesks());
}
const Desk* DesksController::FindDeskOfWindow(aura::Window* window) const {
DCHECK(window);
for (const auto& desk : desks_) {
if (base::Contains(desk->windows(), window))
return desk.get();
}
return nullptr;
}
void DesksController::ReportNumberOfWindowsPerDeskHistogram() const {
for (size_t i = 0; i < desks_.size(); ++i) {
const size_t windows_count = desks_[i]->windows().size();
switch (i) {
case 0:
UMA_HISTOGRAM_COUNTS_100(kNumberOfWindowsOnDesk_1_HistogramName,
windows_count);
break;
case 1:
UMA_HISTOGRAM_COUNTS_100(kNumberOfWindowsOnDesk_2_HistogramName,
windows_count);
break;
case 2:
UMA_HISTOGRAM_COUNTS_100(kNumberOfWindowsOnDesk_3_HistogramName,
windows_count);
break;
case 3:
UMA_HISTOGRAM_COUNTS_100(kNumberOfWindowsOnDesk_4_HistogramName,
windows_count);
break;
case 4:
UMA_HISTOGRAM_COUNTS_100(kNumberOfWindowsOnDesk_5_HistogramName,
windows_count);
break;
case 5:
UMA_HISTOGRAM_COUNTS_100(kNumberOfWindowsOnDesk_6_HistogramName,
windows_count);
break;
case 6:
UMA_HISTOGRAM_COUNTS_100(kNumberOfWindowsOnDesk_7_HistogramName,
windows_count);
break;
case 7:
UMA_HISTOGRAM_COUNTS_100(kNumberOfWindowsOnDesk_8_HistogramName,
windows_count);
break;
default:
NOTREACHED();
break;
}
}
}
void DesksController::ReportDesksCountHistogram() const {
DCHECK_LE(desks_.size(), desks_util::GetMaxNumberOfDesks());
UMA_HISTOGRAM_EXACT_LINEAR(kDesksCountHistogramName, desks_.size(),
desks_util::GetMaxNumberOfDesks());
}
void DesksController::UpdateDesksDefaultNames() {
size_t i = 0;
for (auto& desk : desks_) {
// Do not overwrite user-modified desks' names.
if (!desk->is_name_set_by_user())
desk->SetName(GetDeskDefaultName(i), /*set_by_user=*/false);
i++;
}
}
} // namespace ash