// 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/public/cpp/autotest_private_api_utils.h" #include "ash/app_list/app_list_controller_impl.h" #include "ash/app_list/app_list_presenter_impl.h" #include "ash/frame/non_client_frame_view_ash.h" #include "ash/shell.h" #include "ash/wm/mru_window_tracker.h" #include "ash/wm/tablet_mode/scoped_skip_user_session_blocked_check.h" #include "base/bind.h" #include "base/callback_helpers.h" #include "base/scoped_observation.h" #include "third_party/abseil-cpp/absl/types/optional.h" #include "ui/compositor/layer.h" #include "ui/compositor/layer_animation_observer.h" #include "ui/compositor/layer_animator.h" namespace ash { namespace { class HomeLauncherStateWaiter { public: HomeLauncherStateWaiter(bool target_shown, base::OnceClosure closure) : target_shown_(target_shown), closure_(std::move(closure)) { Shell::Get() ->app_list_controller() ->SetHomeLauncherAnimationCallbackForTesting(base::BindRepeating( &HomeLauncherStateWaiter::OnHomeLauncherAnimationCompleted, base::Unretained(this))); } HomeLauncherStateWaiter(const HomeLauncherStateWaiter&) = delete; HomeLauncherStateWaiter& operator=(const HomeLauncherStateWaiter&) = delete; ~HomeLauncherStateWaiter() { Shell::Get() ->app_list_controller() ->SetHomeLauncherAnimationCallbackForTesting(base::NullCallback()); } private: // Passed to AppListControllerImpl as a callback to run when home launcher // transition animation is complete. void OnHomeLauncherAnimationCompleted(bool shown) { if (shown == target_shown_) { std::move(closure_).Run(); delete this; } } bool target_shown_; base::OnceClosure closure_; }; // A waiter that waits until the animation ended with the target state, and // execute the callback. This self destruction upon completion. class LauncherStateWaiter { public: LauncherStateWaiter(ash::AppListViewState state, base::OnceClosure closure) : target_state_(state), closure_(std::move(closure)) { Shell::Get() ->app_list_controller() ->SetStateTransitionAnimationCallbackForTesting(base::BindRepeating( &LauncherStateWaiter::OnStateChanged, base::Unretained(this))); } LauncherStateWaiter(const LauncherStateWaiter&) = delete; LauncherStateWaiter& operator=(const LauncherStateWaiter&) = delete; ~LauncherStateWaiter() { Shell::Get() ->app_list_controller() ->SetStateTransitionAnimationCallbackForTesting(base::NullCallback()); } void OnStateChanged(ash::AppListViewState state) { if (target_state_ == state) { std::move(closure_).Run(); delete this; } } private: ash::AppListViewState target_state_; base::OnceClosure closure_; }; class LauncherAnimationWaiter : public ui::LayerAnimationObserver { public: LauncherAnimationWaiter(AppListView* view, base::OnceClosure closure) : closure_(std::move(closure)) { observation_.Observe(view->GetWidget()->GetLayer()->GetAnimator()); } ~LauncherAnimationWaiter() override = default; LauncherAnimationWaiter(const LauncherAnimationWaiter&) = delete; LauncherAnimationWaiter& operator=(const LauncherAnimationWaiter&) = delete; private: // ui::LayerAnimationObserver: void OnLayerAnimationEnded(ui::LayerAnimationSequence* sequence) override { std::move(closure_).Run(); delete this; } void OnLayerAnimationAborted(ui::LayerAnimationSequence* sequence) override { OnLayerAnimationEnded(sequence); } void OnLayerAnimationScheduled( ui::LayerAnimationSequence* sequence) override {} base::OnceClosure closure_; base::ScopedObservation observation_{this}; }; bool WaitForHomeLauncherState(bool target_visible, base::OnceClosure closure) { if (Shell::Get()->app_list_controller()->IsVisible( /*display_id=*/absl::nullopt) == target_visible) { std::move(closure).Run(); return true; } new HomeLauncherStateWaiter(target_visible, std::move(closure)); return false; } bool WaitForLauncherAnimation(base::OnceClosure closure) { auto* app_list_view = Shell::Get()->app_list_controller()->fullscreen_presenter()->GetView(); if (!app_list_view) { std::move(closure).Run(); return true; } bool animating = app_list_view->GetWidget()->GetLayer()->GetAnimator()->is_animating(); if (!animating) { std::move(closure).Run(); return true; } new LauncherAnimationWaiter(app_list_view, std::move(closure)); return false; } } // namespace std::vector GetAppWindowList() { ScopedSkipUserSessionBlockedCheck skip_session_blocked; return Shell::Get()->mru_window_tracker()->BuildAppWindowList(kAllDesks); } bool WaitForLauncherState(AppListViewState target_state, base::OnceClosure closure) { const bool in_tablet_mode = Shell::Get()->tablet_mode_controller()->InTabletMode(); if (in_tablet_mode) { // App-list can't enter kPeeking or kHalf state in tablet mode. Thus // |target_state| should be either kClosed, kFullscreenAllApps or // kFullscreenSearch. DCHECK(target_state == AppListViewState::kClosed || target_state == AppListViewState::kFullscreenAllApps || target_state == AppListViewState::kFullscreenSearch); } // In the tablet mode, home launcher visibility state needs special handling, // as app list view visibility does not match home launcher visibility. The // app list view is always visible, but the home launcher may be obscured by // app windows. The waiter interprets waits for kClosed state as waits // "home launcher not visible" state - note that the app list view // is actually expected to be in a visible state. AppListViewState effective_target_state = in_tablet_mode && target_state == AppListViewState::kClosed ? AppListViewState::kFullscreenAllApps : target_state; absl::optional target_home_launcher_visibility; if (in_tablet_mode) target_home_launcher_visibility = target_state != AppListViewState::kClosed; // Don't wait if the launcher is already in the target state and not // animating. auto* app_list_view = Shell::Get()->app_list_controller()->fullscreen_presenter()->GetView(); bool animating = app_list_view && app_list_view->GetWidget()->GetLayer()->GetAnimator()->is_animating(); bool at_target_state = (!app_list_view && effective_target_state == AppListViewState::kClosed) || (app_list_view && app_list_view->app_list_state() == effective_target_state); if (at_target_state && !animating) { // In tablet mode, ensure that the home launcher is in the expected state. if (target_home_launcher_visibility.has_value()) { return WaitForHomeLauncherState(*target_home_launcher_visibility, std::move(closure)); } std::move(closure).Run(); return true; } // In tablet mode, ensure that the home launcher is in the expected state. base::OnceClosure callback = target_home_launcher_visibility.has_value() ? base::BindOnce(base::IgnoreResult(&WaitForHomeLauncherState), *target_home_launcher_visibility, std::move(closure)) : std::move(closure); if (at_target_state) return WaitForLauncherAnimation(std::move(callback)); new LauncherStateWaiter( target_state, base::BindOnce(base::IgnoreResult(&WaitForLauncherAnimation), std::move(callback))); return false; } } // namespace ash