| // Copyright 2018 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/desk.h" |
| |
| #include <algorithm> |
| #include <utility> |
| |
| #include "ash/public/cpp/app_types.h" |
| #include "ash/public/cpp/ash_features.h" |
| #include "ash/public/cpp/shell_window_ids.h" |
| #include "ash/public/cpp/window_properties.h" |
| #include "ash/shell.h" |
| #include "ash/wm/desks/desks_controller.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/window_positioner.h" |
| #include "ash/wm/window_state.h" |
| #include "ash/wm/window_transient_descendant_iterator.h" |
| #include "ash/wm/window_util.h" |
| #include "ash/wm/workspace/backdrop_controller.h" |
| #include "ash/wm/workspace/workspace_layout_manager.h" |
| #include "ash/wm/workspace_controller.h" |
| #include "base/containers/adapters.h" |
| #include "base/containers/contains.h" |
| #include "base/macros.h" |
| #include "base/metrics/histogram_functions.h" |
| #include "base/no_destructor.h" |
| #include "base/stl_util.h" |
| #include "chromeos/ui/base/window_properties.h" |
| #include "ui/aura/client/aura_constants.h" |
| #include "ui/aura/window_tracker.h" |
| #include "ui/display/screen.h" |
| #include "ui/wm/core/window_util.h" |
| |
| namespace ash { |
| |
| namespace { |
| |
| // The name of the histogram for consecutive daily visits. |
| constexpr char kConsecutiveDailyVisitsHistogramName[] = |
| "Ash.Desks.ConsecutiveDailyVisits"; |
| |
| // Prefix for the desks lifetime histogram. |
| constexpr char kDeskLifetimeHistogramNamePrefix[] = "Ash.Desks.DeskLifetime_"; |
| |
| void UpdateBackdropController(aura::Window* desk_container) { |
| auto* workspace_controller = GetWorkspaceController(desk_container); |
| // Work might have already been cleared when the display is removed. See |
| // |RootWindowController::MoveWindowsTo()|. |
| if (!workspace_controller) |
| return; |
| |
| WorkspaceLayoutManager* layout_manager = |
| workspace_controller->layout_manager(); |
| BackdropController* backdrop_controller = |
| layout_manager->backdrop_controller(); |
| backdrop_controller->OnDeskContentChanged(); |
| } |
| |
| // Returns true if |window| can be managed by the desk, and therefore can be |
| // moved out of the desk when the desk is removed. |
| bool CanMoveWindowOutOfDeskContainer(aura::Window* window) { |
| // The desks bar widget is an activatable window placed in the active desk's |
| // container, therefore it should be allowed to move outside of its desk when |
| // its desk is removed. |
| if (window->id() == kShellWindowId_DesksBarWindow) |
| return true; |
| |
| // We never move transient descendants directly, this is taken care of by |
| // `wm::TransientWindowManager::OnWindowHierarchyChanged()`. |
| auto* transient_root = ::wm::GetTransientRoot(window); |
| if (transient_root != window) |
| return false; |
| |
| // Only allow app windows to move to other desks. |
| return window->GetProperty(aura::client::kAppType) != |
| static_cast<int>(AppType::NON_APP); |
| } |
| |
| // Adjusts the z-order stacking of |window_to_fix| in its parent to match its |
| // order in the MRU window list. This is done after the window is moved from one |
| // desk container to another by means of calling AddChild() which adds it as the |
| // top-most window, which doesn't necessarily match the MRU order. |
| // |window_to_fix| must be a child of a desk container, and the root of a |
| // transient hierarchy (if it belongs to one). |
| // This function must be called AddChild() was called to add the |window_to_fix| |
| // (i.e. |window_to_fix| is the top-most window or the top-most window is a |
| // transient child of |window_to_fix|). |
| void FixWindowStackingAccordingToGlobalMru(aura::Window* window_to_fix) { |
| aura::Window* container = window_to_fix->parent(); |
| DCHECK(desks_util::IsDeskContainer(container)); |
| DCHECK_EQ(window_to_fix, wm::GetTransientRoot(window_to_fix)); |
| DCHECK(window_to_fix == container->children().back() || |
| window_to_fix == wm::GetTransientRoot(container->children().back())); |
| |
| const auto mru_windows = |
| Shell::Get()->mru_window_tracker()->BuildWindowListIgnoreModal( |
| DesksMruType::kAllDesks); |
| // Find the closest sibling that is not a transient descendant, which |
| // |window_to_fix| should be stacked below. |
| aura::Window* closest_sibling_above_window = nullptr; |
| for (auto* window : mru_windows) { |
| if (window == window_to_fix) { |
| if (closest_sibling_above_window) |
| container->StackChildBelow(window_to_fix, closest_sibling_above_window); |
| return; |
| } |
| |
| if (window->parent() == container && |
| !wm::HasTransientAncestor(window, window_to_fix)) { |
| closest_sibling_above_window = window; |
| } |
| } |
| } |
| |
| // Returns Jan 1, 2010 00:00:00 as a base::Time object in the local timezone. |
| base::Time GetLocalEpoch() { |
| static base::NoDestructor<base::Time> local_epoch; |
| if (local_epoch->is_null()) { |
| ignore_result(base::Time::FromLocalExploded({2010, 1, 5, 1, 0, 0, 0, 0}, |
| local_epoch.get())); |
| } |
| return *local_epoch; |
| } |
| |
| // Used to temporarily turn off the automatic window positioning while windows |
| // are being moved between desks. |
| class ScopedWindowPositionerDisabler { |
| public: |
| ScopedWindowPositionerDisabler() { |
| WindowPositioner::DisableAutoPositioning(true); |
| } |
| |
| ~ScopedWindowPositionerDisabler() { |
| WindowPositioner::DisableAutoPositioning(false); |
| } |
| |
| private: |
| DISALLOW_COPY_AND_ASSIGN(ScopedWindowPositionerDisabler); |
| }; |
| |
| } // namespace |
| |
| class DeskContainerObserver : public aura::WindowObserver { |
| public: |
| DeskContainerObserver(Desk* owner, aura::Window* container) |
| : owner_(owner), container_(container) { |
| DCHECK_EQ(container_->id(), owner_->container_id()); |
| container->AddObserver(this); |
| } |
| |
| ~DeskContainerObserver() override { container_->RemoveObserver(this); } |
| |
| // aura::WindowObserver: |
| void OnWindowAdded(aura::Window* new_window) override { |
| // TODO(afakhry): Overview mode creates a new widget for each window under |
| // the same parent for the OverviewItemView. We will be notified with |
| // this window addition here. Consider ignoring these windows if they cause |
| // problems. |
| owner_->AddWindowToDesk(new_window); |
| MaybeNotifyAllDesksOfContentChange(new_window); |
| } |
| |
| void OnWindowRemoved(aura::Window* removed_window) override { |
| // We listen to `OnWindowRemoved()` as opposed to `OnWillRemoveWindow()` |
| // since we want to refresh the mini_views only after the window has been |
| // removed from the window tree hierarchy. |
| owner_->RemoveWindowFromDesk(removed_window); |
| MaybeNotifyAllDesksOfContentChange(removed_window); |
| } |
| |
| void OnWindowDestroyed(aura::Window* window) override { |
| // We should never get here. We should be notified in |
| // `OnRootWindowClosing()` before the child containers of the root window |
| // are destroyed, and this object should have already been destroyed. |
| NOTREACHED(); |
| } |
| |
| private: |
| void MaybeNotifyAllDesksOfContentChange(aura::Window* window) { |
| // If a visible on all desks window is added/removed from a desk, only the |
| // desks directly involved will know about their contents changing since it |
| // only resides on the active desk. Since visible on all desks windows |
| // appear in each desks' preview view, we need to notify each desk. |
| auto* desks_controller = DesksController::Get(); |
| if (desks_controller->visible_on_all_desks_windows().contains(window)) |
| desks_controller->NotifyAllDesksForContentChanged(); |
| } |
| |
| Desk* const owner_; |
| aura::Window* const container_; |
| |
| DISALLOW_COPY_AND_ASSIGN(DeskContainerObserver); |
| }; |
| |
| // ----------------------------------------------------------------------------- |
| // Desk: |
| |
| Desk::Desk(int associated_container_id) |
| : container_id_(associated_container_id), |
| creation_time_(base::Time::Now()) { |
| // For the very first default desk added during initialization, there won't be |
| // any root windows yet. That's OK, OnRootWindowAdded() will be called |
| // explicitly by the RootWindowController when they're initialized. |
| for (aura::Window* root : Shell::GetAllRootWindows()) |
| OnRootWindowAdded(root); |
| } |
| |
| Desk::~Desk() { |
| #if DCHECK_IS_ON() |
| for (auto* window : windows_) { |
| DCHECK(!CanMoveWindowOutOfDeskContainer(window)) |
| << "DesksController should remove this desk's application windows " |
| "first."; |
| } |
| #endif |
| |
| for (auto& observer : observers_) { |
| observers_.RemoveObserver(&observer); |
| observer.OnDeskDestroyed(this); |
| } |
| } |
| |
| void Desk::AddObserver(Observer* observer) { |
| observers_.AddObserver(observer); |
| } |
| |
| void Desk::RemoveObserver(Observer* observer) { |
| observers_.RemoveObserver(observer); |
| } |
| |
| void Desk::OnRootWindowAdded(aura::Window* root) { |
| DCHECK(!roots_to_containers_observers_.count(root)); |
| |
| // No windows should be added to the desk container on |root| prior to |
| // tracking it by the desk. |
| aura::Window* desk_container = root->GetChildById(container_id_); |
| DCHECK(desk_container->children().empty()); |
| auto container_observer = |
| std::make_unique<DeskContainerObserver>(this, desk_container); |
| roots_to_containers_observers_.emplace(root, std::move(container_observer)); |
| } |
| |
| void Desk::OnRootWindowClosing(aura::Window* root) { |
| const size_t count = roots_to_containers_observers_.erase(root); |
| DCHECK(count); |
| |
| // The windows on this root are about to be destroyed. We already stopped |
| // observing the container above, so we won't get a call to |
| // DeskContainerObserver::OnWindowRemoved(). Therefore, we must remove those |
| // windows manually. If this is part of shutdown (i.e. when the |
| // RootWindowController is being destroyed), then we're done with those |
| // windows. If this is due to a display being removed, then the |
| // WindowTreeHostManager will move those windows to another host/root, and |
| // they will be added again to the desk container on the new root. |
| const auto windows = windows_; |
| for (auto* window : windows) { |
| if (window->GetRootWindow() == root) |
| base::Erase(windows_, window); |
| } |
| } |
| |
| void Desk::AddWindowToDesk(aura::Window* window) { |
| DCHECK(!base::Contains(windows_, window)); |
| windows_.push_back(window); |
| // No need to refresh the mini_views if the destroyed window doesn't show up |
| // there in the first place. Also don't refresh for visible on all desks |
| // windows since they're already refreshed in OnWindowAdded(). |
| if (!window->GetProperty(kHideInDeskMiniViewKey) && |
| !window->GetProperty(aura::client::kVisibleOnAllWorkspacesKey)) { |
| NotifyContentChanged(); |
| } |
| |
| // Update the window's workspace to this parent desk. |
| if ((features::IsBentoEnabled() || features::IsFullRestoreEnabled()) && |
| !is_desk_being_removed_) { |
| auto* desks_controller = DesksController::Get(); |
| window->SetProperty(aura::client::kWindowWorkspaceKey, |
| desks_controller->GetDeskIndex(this)); |
| } |
| } |
| |
| void Desk::RemoveWindowFromDesk(aura::Window* window) { |
| DCHECK(base::Contains(windows_, window)); |
| base::Erase(windows_, window); |
| // No need to refresh the mini_views if the destroyed window doesn't show up |
| // there in the first place. Also don't refresh for visible on all desks |
| // windows since they're already refreshed in OnWindowRemoved(). |
| if (!window->GetProperty(kHideInDeskMiniViewKey) && |
| !window->GetProperty(aura::client::kVisibleOnAllWorkspacesKey)) { |
| NotifyContentChanged(); |
| } |
| } |
| |
| base::AutoReset<bool> Desk::GetScopedNotifyContentChangedDisabler() { |
| return base::AutoReset<bool>(&should_notify_content_changed_, false); |
| } |
| |
| void Desk::SetName(std::u16string new_name, bool set_by_user) { |
| // Even if the user focuses the DeskNameView for the first time and hits enter |
| // without changing the desk's name (i.e. |new_name| is the same, |
| // |is_name_set_by_user_| is false, and |set_by_user| is true), we don't |
| // change |is_name_set_by_user_| and keep considering the name as a default |
| // name. |
| if (name_ == new_name) |
| return; |
| |
| name_ = std::move(new_name); |
| is_name_set_by_user_ = set_by_user; |
| |
| for (auto& observer : observers_) |
| observer.OnDeskNameChanged(name_); |
| } |
| |
| void Desk::PrepareForActivationAnimation() { |
| DCHECK(!is_active_); |
| |
| for (aura::Window* root : Shell::GetAllRootWindows()) { |
| auto* container = root->GetChildById(container_id_); |
| container->layer()->SetOpacity(0); |
| container->Show(); |
| } |
| started_activation_animation_ = true; |
| } |
| |
| void Desk::Activate(bool update_window_activation) { |
| DCHECK(!is_active_); |
| |
| if (!MaybeResetContainersOpacities()) { |
| for (aura::Window* root : Shell::GetAllRootWindows()) |
| root->GetChildById(container_id_)->Show(); |
| } |
| |
| is_active_ = true; |
| |
| if (!IsConsecutiveDailyVisit()) |
| RecordAndResetConsecutiveDailyVisits(/*being_removed=*/false); |
| |
| int current_date = GetDaysFromLocalEpoch(); |
| if (current_date < last_day_visited_ || first_day_visited_ == -1) { |
| // If |current_date| < |last_day_visited_| then the user has moved timezones |
| // or the stored data has been corrupted so reset |first_day_visited_|. |
| first_day_visited_ = current_date; |
| } |
| last_day_visited_ = current_date; |
| |
| if (!update_window_activation || windows_.empty()) |
| return; |
| |
| // Activate the window on this desk that was most recently used right before |
| // the user switched to another desk, so as not to break the user's workflow. |
| for (auto* window : |
| Shell::Get()->mru_window_tracker()->BuildMruWindowList(kActiveDesk)) { |
| if (!base::Contains(windows_, window)) |
| continue; |
| |
| // Do not activate minimized windows, otherwise they will unminimize. |
| if (WindowState::Get(window)->IsMinimized()) |
| continue; |
| |
| wm::ActivateWindow(window); |
| return; |
| } |
| } |
| |
| void Desk::Deactivate(bool update_window_activation) { |
| DCHECK(is_active_); |
| |
| auto* active_window = window_util::GetActiveWindow(); |
| |
| // Hide the associated containers on all roots. |
| for (aura::Window* root : Shell::GetAllRootWindows()) |
| root->GetChildById(container_id_)->Hide(); |
| |
| is_active_ = false; |
| last_day_visited_ = GetDaysFromLocalEpoch(); |
| |
| if (!update_window_activation) |
| return; |
| |
| // Deactivate the active window (if it belongs to this desk; active window may |
| // be on a different container, or one of the widgets created by overview mode |
| // which are not considered desk windows) after this desk's associated |
| // containers have been hidden. This is to prevent the focus controller from |
| // activating another window on the same desk when the active window loses |
| // focus. |
| if (active_window && base::Contains(windows_, active_window)) |
| wm::DeactivateWindow(active_window); |
| } |
| |
| void Desk::MoveWindowsToDesk(Desk* target_desk) { |
| DCHECK(target_desk); |
| |
| { |
| ScopedWindowPositionerDisabler window_positioner_disabler; |
| |
| // Throttle notifying the observers, while we move those windows and notify |
| // them only once when done. |
| auto this_desk_throttled = GetScopedNotifyContentChangedDisabler(); |
| auto target_desk_throttled = |
| target_desk->GetScopedNotifyContentChangedDisabler(); |
| |
| // Moving windows will change the hierarchy and hence |windows_|, and has to |
| // be done without changing the relative z-order. So we make a copy of all |
| // the top-level windows on all the containers of this desk, such that |
| // windows in each container are copied from top-most (z-order) to |
| // bottom-most. |
| // Note that moving windows out of the container and restacking them |
| // differently may trigger events that lead to destroying a window on the |
| // list. For example moving the top-most window which has a backdrop will |
| // cause the backdrop to be destroyed. Therefore observe such events using |
| // an |aura::WindowTracker|. |
| aura::WindowTracker windows_to_move; |
| for (aura::Window* root : Shell::GetAllRootWindows()) { |
| const aura::Window* container = GetDeskContainerForRoot(root); |
| for (auto* window : base::Reversed(container->children())) |
| windows_to_move.Add(window); |
| } |
| |
| auto* mru_tracker = Shell::Get()->mru_window_tracker(); |
| while (!windows_to_move.windows().empty()) { |
| auto* window = windows_to_move.Pop(); |
| if (!CanMoveWindowOutOfDeskContainer(window)) |
| continue; |
| |
| // Note that windows that belong to the same container in |
| // |windows_to_move| are sorted from top-most to bottom-most, hence |
| // calling |StackChildAtBottom()| on each in this order will maintain that |
| // same order in the |target_desk|'s container. |
| MoveWindowToDeskInternal(window, target_desk, window->GetRootWindow()); |
| window->parent()->StackChildAtBottom(window); |
| mru_tracker->OnWindowMovedOutFromRemovingDesk(window); |
| } |
| } |
| |
| NotifyContentChanged(); |
| target_desk->NotifyContentChanged(); |
| } |
| |
| void Desk::MoveWindowToDesk(aura::Window* window, |
| Desk* target_desk, |
| aura::Window* target_root) { |
| DCHECK(window); |
| DCHECK(target_desk); |
| DCHECK(target_root); |
| DCHECK(base::Contains(windows_, window)); |
| DCHECK(this != target_desk); |
| // The desks bar should not be allowed to move individually to another desk. |
| // Only as part of `MoveWindowsToDesk()` when the desk is removed. |
| DCHECK_NE(window->id(), kShellWindowId_DesksBarWindow); |
| |
| { |
| ScopedWindowPositionerDisabler window_positioner_disabler; |
| |
| // Throttling here is necessary even though we're attempting to move a |
| // single window. This is because that window might exist in a transient |
| // window tree, which will result in actually moving multiple windows if the |
| // transient children used to be on the same container. |
| // See `wm::TransientWindowManager::OnWindowHierarchyChanged()`. |
| auto this_desk_throttled = GetScopedNotifyContentChangedDisabler(); |
| auto target_desk_throttled = |
| target_desk->GetScopedNotifyContentChangedDisabler(); |
| |
| // Always move the root of the transient window tree. We should never move a |
| // transient child and leave its parent behind. Moving the transient |
| // descendants that exist on the same desk container will be taken care of |
| // by `wm::TransientWindowManager::OnWindowHierarchyChanged()`. |
| aura::Window* transient_root = ::wm::GetTransientRoot(window); |
| MoveWindowToDeskInternal(transient_root, target_desk, target_root); |
| FixWindowStackingAccordingToGlobalMru(transient_root); |
| |
| // Unminimize the window so that it shows up in the mini_view after it had |
| // been dragged and moved to another desk. Don't unminimize if the window is |
| // visible on all desks since it's being moved during desk activation. |
| auto* window_state = WindowState::Get(transient_root); |
| if (window_state->IsMinimized() && |
| !window->GetProperty(aura::client::kVisibleOnAllWorkspacesKey)) { |
| window_state->Unminimize(); |
| } |
| } |
| |
| NotifyContentChanged(); |
| target_desk->NotifyContentChanged(); |
| } |
| |
| aura::Window* Desk::GetDeskContainerForRoot(aura::Window* root) const { |
| DCHECK(root); |
| |
| return root->GetChildById(container_id_); |
| } |
| |
| void Desk::NotifyContentChanged() { |
| if (!should_notify_content_changed_) |
| return; |
| |
| // Updating the backdrops below may lead to the removal or creation of |
| // backdrop windows in this desk, which can cause us to recurse back here. |
| // Disable this. |
| auto disable_recursion = GetScopedNotifyContentChangedDisabler(); |
| |
| // The availability and visibility of backdrops of all containers associated |
| // with this desk will be updated *before* notifying observer, so that the |
| // mini_views update *after* the backdrops do. |
| // This is *only* needed if the WorkspaceLayoutManager won't take care of this |
| // for us while overview is active. |
| if (Shell::Get()->overview_controller()->InOverviewSession()) |
| UpdateDeskBackdrops(); |
| |
| for (auto& observer : observers_) |
| observer.OnContentChanged(); |
| } |
| |
| void Desk::UpdateDeskBackdrops() { |
| for (auto* root : Shell::GetAllRootWindows()) |
| UpdateBackdropController(GetDeskContainerForRoot(root)); |
| } |
| |
| void Desk::SetDeskBeingRemoved() { |
| is_desk_being_removed_ = true; |
| } |
| |
| void Desk::RecordLifetimeHistogram() { |
| // Desk index is 1-indexed in histograms. |
| const int desk_index = |
| Shell::Get()->desks_controller()->GetDeskIndex(this) + 1; |
| base::UmaHistogramCounts1000( |
| base::StringPrintf("%s%i", kDeskLifetimeHistogramNamePrefix, desk_index), |
| (base::Time::Now() - creation_time_).InHours()); |
| } |
| |
| bool Desk::IsConsecutiveDailyVisit() const { |
| if (last_day_visited_ == -1) |
| return true; |
| |
| const int days_since_last_visit = GetDaysFromLocalEpoch() - last_day_visited_; |
| return days_since_last_visit <= 1; |
| } |
| |
| void Desk::RecordAndResetConsecutiveDailyVisits(bool being_removed) { |
| if (being_removed && is_active_) { |
| // When the user removes the active desk, update |last_day_visited_| to the |
| // current day to account for the time they spent on this desk. |
| last_day_visited_ = GetDaysFromLocalEpoch(); |
| } |
| |
| const int consecutive_daily_visits = |
| last_day_visited_ - first_day_visited_ + 1; |
| DCHECK_GE(consecutive_daily_visits, 1); |
| base::UmaHistogramCounts1000(kConsecutiveDailyVisitsHistogramName, |
| consecutive_daily_visits); |
| |
| last_day_visited_ = -1; |
| first_day_visited_ = -1; |
| } |
| |
| int Desk::GetDaysFromLocalEpoch() const { |
| base::Time now = override_clock_ ? override_clock_->Now() : base::Time::Now(); |
| return (now - GetLocalEpoch()).InDays(); |
| } |
| |
| void Desk::OverrideClockForTesting(base::Clock* test_clock) { |
| DCHECK(!override_clock_); |
| override_clock_ = test_clock; |
| } |
| |
| void Desk::ResetVisitedMetricsForTesting() { |
| const int current_date = GetDaysFromLocalEpoch(); |
| first_day_visited_ = current_date; |
| last_day_visited_ = current_date; |
| } |
| |
| void Desk::MoveWindowToDeskInternal(aura::Window* window, |
| Desk* target_desk, |
| aura::Window* target_root) { |
| DCHECK(base::Contains(windows_, window)); |
| DCHECK(CanMoveWindowOutOfDeskContainer(window)) |
| << "Non-desk windows are not allowed to move out of the container."; |
| |
| // When |target_root| is different than the current window's |root|, this can |
| // only happen when dragging and dropping a window on mini desk view on |
| // another display. Therefore |target_desk| is an inactive desk (i.e. |
| // invisible). The order doesn't really matter, but we move the window to the |
| // target desk's container first (so that it becomes hidden), then move it to |
| // the target display (while it's hidden). |
| aura::Window* root = window->GetRootWindow(); |
| aura::Window* source_container = GetDeskContainerForRoot(root); |
| aura::Window* target_container = target_desk->GetDeskContainerForRoot(root); |
| DCHECK(window->parent() == source_container); |
| target_container->AddChild(window); |
| |
| if (root != target_root) { |
| // Move the window to the container with the same ID on the target display's |
| // root (i.e. container that belongs to the same desk), and adjust its |
| // bounds to fit in the new display's work area. |
| window_util::MoveWindowToDisplay(window, |
| display::Screen::GetScreen() |
| ->GetDisplayNearestWindow(target_root) |
| .id()); |
| DCHECK_EQ(target_desk->container_id_, window->parent()->id()); |
| } |
| } |
| |
| bool Desk::MaybeResetContainersOpacities() { |
| if (!started_activation_animation_) |
| return false; |
| |
| for (aura::Window* root : Shell::GetAllRootWindows()) { |
| auto* container = root->GetChildById(container_id_); |
| container->layer()->SetOpacity(1); |
| } |
| started_activation_animation_ = false; |
| return true; |
| } |
| |
| } // namespace ash |