| // 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 |