| // Copyright 2013 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "ash/wm/lock_state_controller.h" |
| |
| #include <algorithm> |
| #include <string> |
| #include <utility> |
| |
| #include "ash/accessibility/accessibility_controller.h" |
| #include "ash/app_list/app_list_controller_impl.h" |
| #include "ash/cancel_mode.h" |
| #include "ash/capture_mode/capture_mode_controller.h" |
| #include "ash/constants/ash_pref_names.h" |
| #include "ash/multi_user/multi_user_window_manager.h" |
| #include "ash/public/cpp/saved_desk_delegate.h" |
| #include "ash/public/cpp/shell_window_ids.h" |
| #include "ash/public/cpp/shutdown_controller.h" |
| #include "ash/root_window_controller.h" |
| #include "ash/session/session_controller_impl.h" |
| #include "ash/shell.h" |
| #include "ash/shell_delegate.h" |
| #include "ash/utility/occlusion_tracker_pauser.h" |
| #include "ash/wallpaper/views/wallpaper_view.h" |
| #include "ash/wallpaper/views/wallpaper_widget_controller.h" |
| #include "ash/wallpaper/wallpaper_controller_impl.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/screen_pinning_controller.h" |
| #include "ash/wm/session_state_animator_impl.h" |
| #include "ash/wm/window_restore/informed_restore_constants.h" |
| #include "ash/wm/window_restore/window_restore_metrics.h" |
| #include "ash/wm/window_restore/window_restore_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/span.h" |
| #include "base/debug/crash_logging.h" |
| #include "base/debug/dump_without_crashing.h" |
| #include "base/files/file_path.h" |
| #include "base/files/file_util.h" |
| #include "base/functional/bind.h" |
| #include "base/functional/callback_helpers.h" |
| #include "base/json/values_util.h" |
| #include "base/location.h" |
| #include "base/logging.h" |
| #include "base/metrics/histogram_functions.h" |
| #include "base/metrics/histogram_macros.h" |
| #include "base/metrics/user_metrics.h" |
| #include "base/notreached.h" |
| #include "base/strings/string_util.h" |
| #include "base/system/sys_info.h" |
| #include "base/task/bind_post_task.h" |
| #include "base/task/current_thread.h" |
| #include "base/task/task_traits.h" |
| #include "base/task/thread_pool.h" |
| #include "base/time/default_clock.h" |
| #include "base/time/time.h" |
| #include "chromeos/dbus/power/power_manager_client.h" |
| #include "ui/aura/window_tree_host.h" |
| #include "ui/compositor/layer.h" |
| #include "ui/gfx/image/image.h" |
| #include "ui/gfx/image/image_util.h" |
| #include "ui/snapshot/snapshot.h" |
| #include "ui/views/controls/menu/menu_controller.h" |
| #include "ui/views/widget/widget.h" |
| #include "ui/wm/core/compound_event_filter.h" |
| #include "ui/wm/core/cursor_manager.h" |
| |
| #define UMA_HISTOGRAM_LOCK_TIMES(name, sample) \ |
| UMA_HISTOGRAM_CUSTOM_TIMES(name, sample, base::Milliseconds(1), \ |
| base::Seconds(50), 100) |
| |
| // TODO(b/228873153): Remove after figuring out the root cause of the bug |
| #undef ENABLED_VLOG_LEVEL |
| #define ENABLED_VLOG_LEVEL 1 |
| |
| namespace ash { |
| |
| namespace { |
| |
| // ASan/TSan/MSan instrument each memory access. This may slow the execution |
| // down significantly. |
| #if defined(MEMORY_SANITIZER) |
| // For MSan the slowdown depends heavily on the value of msan_track_origins GYP |
| // flag. The multiplier below corresponds to msan_track_origins=1. |
| constexpr int kTimeoutMultiplier = 6; |
| #elif defined(ADDRESS_SANITIZER) || defined(THREAD_SANITIZER) |
| constexpr int kTimeoutMultiplier = 2; |
| #else |
| constexpr int kTimeoutMultiplier = 1; |
| #endif |
| |
| constexpr int kMaxShutdownSoundDurationMs = 1500; |
| |
| // Amount of time to wait for our lock requests to be honored before giving up. |
| constexpr base::TimeDelta kLockFailTimeout = |
| base::Seconds(8 * kTimeoutMultiplier); |
| |
| // Amount of time to wait for our post lock animation before giving up. |
| constexpr base::TimeDelta kPostLockFailTimeout = |
| base::Seconds(2 * kTimeoutMultiplier); |
| |
| // Additional time to wait after starting the fast-close shutdown animation |
| // before actually requesting shutdown, to give the animation time to finish. |
| constexpr base::TimeDelta kShutdownRequestDelay = base::Milliseconds(50); |
| |
| // Amount of time to wait after starting to take the informed restore |
| // screenshot. The task will be stopped if it takes longer than this time |
| // duration. |
| constexpr base::TimeDelta kTakeScreenshotFailTimeout = base::Milliseconds(800); |
| |
| // Records the given `duration` to the given `pref_name` so it can be recorded |
| // as an UMA metric on the next startup. |
| void SaveInformedRestoreScreenshotDuration(PrefService* local_state, |
| const std::string& pref_name, |
| base::TimeDelta duration) { |
| if (!local_state) { |
| return; |
| } |
| |
| local_state->SetTimeDelta(pref_name, duration); |
| } |
| |
| // Encodes and saves the given `image` to `file_path`. |
| void EncodeAndSaveImage(const base::FilePath& file_path, gfx::Image image) { |
| CHECK(!base::CurrentUIThread::IsSet()); |
| if (image.IsEmpty()) { |
| base::DeleteFile(file_path); |
| return; |
| } |
| |
| // The width of the resized informed restore image will be fixed and then the |
| // height of it will be calculated based on the aspect ratio of the original |
| // informed restore image. The resized informed restore image will be saved to |
| // disk, decoded and shown with this size directly inside the informed restore |
| // dialog later as well. |
| const float aspect_ratio = static_cast<float>(image.Height()) / image.Width(); |
| const int resized_image_height = |
| aspect_ratio * informed_restore::kPreviewContainerWidth; |
| const auto resized_image = gfx::ResizedImage( |
| image, gfx::Size(informed_restore::kPreviewContainerWidth, |
| resized_image_height)); |
| auto png_bytes = resized_image.As1xPNGBytes(); |
| if (!base::WriteFile(file_path, base::span(*png_bytes))) { |
| LOG(ERROR) << "Failed to write informed restore image to " |
| << file_path.MaybeAsASCII(); |
| } |
| } |
| |
| // If the given `for_test_callback` is valid, `callback` will be modified |
| // to be a new callback that runs the original `callback` and then runs |
| // `for_test_callback` after the former finishes. |
| // `base::BindPostTask()` is used to guarantee that when `for_test_callback` |
| // is invoked, it runs on the same thread of the call site (even if `callback` |
| // is posted to run on a different thread). |
| // Note that `for_test_callback` will be empty after this function returns. |
| template <typename Callback> |
| void MaybeAppendTestCallback(Callback& callback, |
| base::OnceClosure& for_test_callback) { |
| if (for_test_callback) { |
| callback = std::move(callback).Then( |
| base::BindPostTask(base::SingleThreadTaskRunner::GetCurrentDefault(), |
| std::move(for_test_callback))); |
| } |
| } |
| |
| // Deletes any existing informed restore image if we should change the session |
| // state without taking the screenshot, then no stale screenshot will be shown |
| // after the session state changes. |
| void DeleteInformedRestoreImage(base::OnceClosure& for_test_callback, |
| const base::FilePath& file_path) { |
| auto delete_image_cb = |
| base::BindOnce(base::IgnoreResult(&base::DeleteFile), file_path); |
| MaybeAppendTestCallback(delete_image_cb, for_test_callback); |
| base::ThreadPool::PostTask(FROM_HERE, |
| {base::MayBlock(), base::TaskPriority::HIGHEST, |
| base::TaskShutdownBehavior::BLOCK_SHUTDOWN}, |
| std::move(delete_image_cb)); |
| } |
| |
| bool IsInformedRestoreEnabledForPrimaryUser() { |
| auto* session_controller_impl = Shell::Get()->session_controller(); |
| CHECK(session_controller_impl); |
| auto* prefs = session_controller_impl->GetPrimaryUserPrefService(); |
| if (!prefs) { |
| // Note: this may be called on the login screen. |
| return false; |
| } |
| return IsAskEveryTime(prefs); |
| } |
| |
| // TODO(minch): Check whether the screenshot should be taken in kiosk mode. |
| // Returns true if the informed restore screenshot should be taken on session |
| // state changes. |
| bool ShouldTakeInformedRestoreScreenshot() { |
| // If the current active user disables the informed restore feature, we should |
| // not take the screenshot. |
| if (!IsInformedRestoreEnabledForPrimaryUser()) { |
| return false; |
| } |
| |
| auto* shell = Shell::Get(); |
| // Do not take the informed restore screenshot if it is in overview mode, lock |
| // screen, home launcher or pinned mode. |
| if (shell->overview_controller()->InOverviewSession()) { |
| RecordScreenshotOnShutdownStatus( |
| ScreenshotOnShutdownStatus::kFailedInOverview); |
| return false; |
| } |
| auto* session_controller = shell->session_controller(); |
| if (session_controller->IsScreenLocked()) { |
| RecordScreenshotOnShutdownStatus( |
| ScreenshotOnShutdownStatus::kFailedInLockScreen); |
| return false; |
| } |
| if (session_controller->IsUserGuest() || |
| session_controller->IsUserPublicAccount()) { |
| RecordScreenshotOnShutdownStatus( |
| ScreenshotOnShutdownStatus::kFailedInGuestOrPublicUserSession); |
| return false; |
| } |
| if (shell->app_list_controller()->IsHomeScreenVisible()) { |
| RecordScreenshotOnShutdownStatus( |
| ScreenshotOnShutdownStatus::kFailedInHomeLauncher); |
| return false; |
| } |
| if (shell->screen_pinning_controller()->IsPinned()) { |
| RecordScreenshotOnShutdownStatus( |
| ScreenshotOnShutdownStatus::kFailedInPinnedMode); |
| return false; |
| } |
| // In MUSI scenario, do not take screenshot if another user's desk is active. |
| if (!session_controller->IsUserPrimary()) { |
| RecordScreenshotOnShutdownStatus( |
| ScreenshotOnShutdownStatus::kFailedOtherUserIsActive); |
| return false; |
| } |
| |
| auto* multi_user_window_manager = MultiUserWindowManager::Get(); |
| CHECK(multi_user_window_manager); |
| |
| const AccountId& current_active_user = |
| multi_user_window_manager->CurrentAccountId(); |
| |
| bool has_regular_unminimized_window = false; |
| for (aura::Window* window : |
| shell->mru_window_tracker()->BuildMruWindowList(kActiveDesk)) { |
| const bool is_minimized = WindowState::Get(window)->IsMinimized(); |
| |
| // Do not take the screenshot if there is an incognito ash browser window. |
| const bool is_non_regular_profile_window = |
| !shell->saved_desk_delegate()->IsWindowPersistable(window); |
| if (!is_minimized && is_non_regular_profile_window) { |
| RecordScreenshotOnShutdownStatus( |
| ScreenshotOnShutdownStatus::kFailedWithIncognito); |
| return false; |
| } |
| has_regular_unminimized_window |= |
| !is_non_regular_profile_window && !is_minimized; |
| |
| // Do not take the screenshot if there is an window that was moved from |
| // another user's desk. |
| AccountId owner = multi_user_window_manager->GetWindowOwner(window); |
| const bool is_window_owned_by_other_user = |
| owner != EmptyAccountId() && owner != current_active_user; |
| if (!is_minimized && is_window_owned_by_other_user) { |
| RecordScreenshotOnShutdownStatus( |
| ScreenshotOnShutdownStatus::kFailedWithVisibleWindowFromOtherUser); |
| return false; |
| } |
| } |
| |
| // Take the screenshot if there are unminimized non-incognito windows inside |
| // the active desk. Both the float and the always on top window will be |
| // counted. |
| if (!has_regular_unminimized_window) { |
| RecordScreenshotOnShutdownStatus( |
| ScreenshotOnShutdownStatus::kFailedWithNoWindows); |
| return false; |
| } |
| |
| return true; |
| } |
| |
| // Hide the cursor and lock the cursor as well if `lock` is true. |
| void HideAndMaybeLockCursor(bool lock) { |
| Shell* shell = Shell::Get(); |
| if (auto* cursor_manager = shell->cursor_manager(); cursor_manager) { |
| // Hide cursor, but let it reappear if the mouse moves. |
| cursor_manager->HideCursor(); |
| if (lock) { |
| cursor_manager->LockCursor(); |
| } |
| } |
| } |
| |
| } // namespace |
| |
| // static |
| const int LockStateController::kPreLockContainersMask = |
| SessionStateAnimator::NON_LOCK_SCREEN_CONTAINERS | |
| SessionStateAnimator::SHELF; |
| |
| LockStateController::LockStateController( |
| ShutdownController* shutdown_controller, |
| PrefService* local_state) |
| : animator_(new SessionStateAnimatorImpl()), |
| shutdown_controller_(shutdown_controller), |
| scoped_session_observer_(this), |
| local_state_(local_state) { |
| DCHECK(shutdown_controller_); |
| Shell::GetPrimaryRootWindow()->GetHost()->AddObserver(this); |
| // |local_state_| could be null in tests. |
| if (local_state_) { |
| // If kLoginShutdownTimestampPrefName is registered, check the last recorded |
| // login shutdown timestamp in local state prefs, in case device was shut |
| // down using shelf button. |
| auto* login_shutdown_timestamp_pref = |
| local_state_->FindPreference(prefs::kLoginShutdownTimestampPrefName); |
| if (login_shutdown_timestamp_pref && |
| !login_shutdown_timestamp_pref->IsDefaultValue()) { |
| base::Time last_recorded_login_shutdown_timestamp = |
| base::ValueToTime(login_shutdown_timestamp_pref->GetValue()).value(); |
| base::TimeDelta duration = base::DefaultClock::GetInstance()->Now() - |
| last_recorded_login_shutdown_timestamp; |
| // Report time delta even if it exceeds histogram limit, to better |
| // understand fraction of users using the feature. |
| base::UmaHistogramLongTimes( |
| "Ash.Shelf.ShutdownConfirmationBubble.TimeToNextBoot." |
| "LoginShutdownToPowerUpDuration", |
| duration); |
| |
| // Reset to the default value after the value is recorded. |
| local_state_->ClearPref(prefs::kLoginShutdownTimestampPrefName); |
| } |
| } |
| } |
| |
| LockStateController::~LockStateController() { |
| Shell::GetPrimaryRootWindow()->GetHost()->RemoveObserver(this); |
| } |
| |
| // static |
| void LockStateController::RegisterPrefs(PrefRegistrySimple* registry) { |
| registry->RegisterTimePref(prefs::kLoginShutdownTimestampPrefName, |
| base::Time()); |
| registry->RegisterTimeDeltaPref( |
| prefs::kInformedRestoreScreenshotTakenDuration, base::TimeDelta()); |
| registry->RegisterTimeDeltaPref( |
| prefs::kInformedRestoreScreenshotEncodeAndSaveDuration, |
| base::TimeDelta()); |
| } |
| |
| void LockStateController::AddObserver(LockStateObserver* observer) { |
| observers_.AddObserver(observer); |
| } |
| |
| void LockStateController::RemoveObserver(LockStateObserver* observer) { |
| observers_.RemoveObserver(observer); |
| } |
| |
| void LockStateController::StartLockAnimation() { |
| if (animating_lock_) { |
| return; |
| } |
| |
| views::MenuController* active_menu_controller = |
| views::MenuController::GetActiveInstance(); |
| |
| if (active_menu_controller) { |
| // TODO(http://b/328064674): Please remove the below crash keys once the |
| // the crash is fixed. It seems after post lock animation finished there |
| // is active menu. This check is moved to the StartLockAnimation, since it |
| // seems the check in the post lock animation is too late. |
| |
| views::Widget* owner = active_menu_controller->owner(); |
| SCOPED_CRASH_KEY_STRING256("LockStateController", "StartLockAnimation", |
| owner ? owner->GetName() : "ownerless"); |
| NOTREACHED(); |
| } |
| |
| animating_lock_ = true; |
| StoreUnlockedProperties(); |
| VLOG(1) << "StartLockAnimation"; |
| PreLockAnimation(SessionStateAnimator::ANIMATION_SPEED_UNDOABLE, true); |
| DispatchCancelMode(); |
| OnLockStateEvent(LockStateObserver::EVENT_PRELOCK_ANIMATION_STARTED); |
| } |
| |
| void LockStateController::LockWithoutAnimation() { |
| VLOG(1) << "LockWithoutAnimation : " |
| << "animating_unlock_: " << static_cast<int>(animating_unlock_) |
| << ", animating_lock_: " << static_cast<int>(animating_lock_); |
| if (animating_unlock_) { |
| CancelUnlockAnimation(); |
| // One would expect a call to |
| // `Shell::Get()->session_controller()->LockScreen()` at this point, |
| // however, when execution reaches here, if: |
| // |
| // We were running the animations started as part of |
| // StartUnlockAnimationBeforeLockUIDestroyed, `session_manager` still |
| // considers the screen to be locked, as we've only executed the part of the |
| // animations done before the lock screen UI is destroyed. |
| // |
| // We were running the animations started as part of |
| // StartUnlockAnimationAfterLockUIDestroyed, `session_manager` would |
| // consider the session to be unlocked, and thus we lock it again as part of |
| // UnlockAnimationAfterLockUIDestroyedFinished. |
| return; |
| } |
| if (animating_lock_) |
| return; |
| animating_lock_ = true; |
| post_lock_immediate_animation_ = true; |
| animator_->StartAnimation(kPreLockContainersMask, |
| SessionStateAnimator::ANIMATION_HIDE_IMMEDIATELY, |
| SessionStateAnimator::ANIMATION_SPEED_IMMEDIATE); |
| OnLockStateEvent(LockStateObserver::EVENT_LOCK_ANIMATION_STARTED); |
| Shell::Get()->session_controller()->LockScreen(); |
| } |
| |
| bool LockStateController::LockRequested() { |
| return lock_fail_timer_.IsRunning(); |
| } |
| |
| void LockStateController::CancelLockAnimation() { |
| VLOG(1) << "CancelLockAnimation"; |
| animating_lock_ = false; |
| Shell::Get()->wallpaper_controller()->RestoreWallpaperBlurForLockState( |
| saved_blur_); |
| auto next_animation_starter = |
| base::BindOnce(&LockStateController::LockAnimationCancelled, |
| weak_ptr_factory_.GetWeakPtr()); |
| SessionStateAnimator::AnimationSequence* animation_sequence = |
| animator_->BeginAnimationSequence(std::move(next_animation_starter)); |
| |
| animation_sequence->StartAnimation( |
| SessionStateAnimator::NON_LOCK_SCREEN_CONTAINERS, |
| SessionStateAnimator::ANIMATION_UNDO_LIFT, |
| SessionStateAnimator::ANIMATION_SPEED_UNDO_MOVE_WINDOWS); |
| animation_sequence->StartAnimation( |
| SessionStateAnimator::SHELF, SessionStateAnimator::ANIMATION_FADE_IN, |
| SessionStateAnimator::ANIMATION_SPEED_UNDO_MOVE_WINDOWS); |
| AnimateWallpaperHidingIfNecessary( |
| SessionStateAnimator::ANIMATION_SPEED_UNDO_MOVE_WINDOWS, |
| animation_sequence); |
| |
| animation_sequence->EndSequence(); |
| } |
| |
| void LockStateController::OnUnlockAnimationBeforeLockUIDestroyedFinished() { |
| if (pb_pressed_during_unlock_) { |
| // Power button was pressed during the unlock animation and |
| // CancelUnlockAnimation was called, restore UI elements to previous state |
| // immediately. |
| animator_->StartAnimation(SessionStateAnimator::SHELF, |
| SessionStateAnimator::ANIMATION_FADE_IN, |
| SessionStateAnimator::ANIMATION_SPEED_IMMEDIATE); |
| animator_->StartAnimation(SessionStateAnimator::LOCK_SCREEN_CONTAINERS, |
| SessionStateAnimator::ANIMATION_UNDO_LIFT, |
| SessionStateAnimator::ANIMATION_SPEED_IMMEDIATE); |
| // We aborted, so we are not animating anymore. |
| animating_unlock_ = false; |
| } |
| std::move(start_unlock_callback_).Run(pb_pressed_during_unlock_); |
| pb_pressed_during_unlock_ = false; |
| } |
| |
| void LockStateController::OnLockScreenHide( |
| SessionStateAnimator::AnimationCallback callback) { |
| start_unlock_callback_ = std::move(callback); |
| StartUnlockAnimationBeforeLockUIDestroyed(base::BindOnce( |
| &LockStateController::OnUnlockAnimationBeforeLockUIDestroyedFinished, |
| weak_ptr_factory_.GetWeakPtr())); |
| } |
| |
| void LockStateController::SetLockScreenDisplayedCallback( |
| base::OnceClosure callback) { |
| DCHECK(lock_screen_displayed_callback_.is_null()); |
| if (system_is_locked_ && !animating_lock_) |
| std::move(callback).Run(); |
| else |
| lock_screen_displayed_callback_ = std::move(callback); |
| } |
| |
| void LockStateController::RequestShutdown(ShutdownReason reason) { |
| if (shutting_down_) { |
| return; |
| } |
| |
| shutting_down_ = true; |
| shutdown_reason_ = reason; |
| shutdown_canceled_ = false; |
| |
| if (reason == ShutdownReason::LOGIN_SHUT_DOWN_BUTTON) { |
| base::Time now_timestamp = base::DefaultClock::GetInstance()->Now(); |
| local_state_->SetTime(prefs::kLoginShutdownTimestampPrefName, |
| now_timestamp); |
| } |
| |
| HideAndMaybeLockCursor(/*lock=*/true); |
| SessionStateChangeWithInformedRestore(RequestedSessionState::kShutdown); |
| } |
| |
| void LockStateController::RequestCancelableShutdown(ShutdownReason reason) { |
| shutdown_reason_ = reason; |
| shutdown_canceled_ = false; |
| |
| HideAndMaybeLockCursor(/*lock=*/false); |
| SessionStateChangeWithInformedRestore( |
| RequestedSessionState::kCancelableShutdown); |
| } |
| |
| bool LockStateController::ShutdownRequested() const { |
| return shutting_down_; |
| } |
| |
| bool LockStateController::MaybeCancelShutdownAnimation() { |
| if (ShutdownRequested()) { |
| return false; |
| } |
| |
| animator_->StartAnimation( |
| SessionStateAnimator::ROOT_CONTAINER, |
| SessionStateAnimator::ANIMATION_UNDO_GRAYSCALE_BRIGHTNESS, |
| SessionStateAnimator::ANIMATION_SPEED_REVERT_SHUTDOWN); |
| shutdown_canceled_ = true; |
| // Shutdown maybe canceled before or after image saved. So we need to delete |
| // both here and `OnImageSaved`. |
| DeleteInformedRestoreImage(informed_restore_image_callback_for_test_, |
| GetInformedRestoreImagePath()); |
| cancelable_shutdown_timer_.Stop(); |
| return true; |
| } |
| |
| void LockStateController::RequestRestart( |
| power_manager::RequestRestartReason reason, |
| const std::string& description) { |
| HideAndMaybeLockCursor(/*lock=*/false); |
| restart_callback_ = |
| base::BindOnce(&LockStateController::DoRestart, base::Unretained(this), |
| reason, description); |
| SessionStateChangeWithInformedRestore(RequestedSessionState::kRestart); |
| } |
| |
| void LockStateController::RequestSignOut() { |
| SessionStateChangeWithInformedRestore(RequestedSessionState::kSignOut); |
| } |
| |
| void LockStateController::OnHostCloseRequested(aura::WindowTreeHost* host) { |
| Shell::Get()->session_controller()->RequestSignOut(); |
| } |
| |
| void LockStateController::OnChromeTerminating() { |
| // If we hear that Chrome is exiting but didn't request it ourselves, all we |
| // can really hope for is that we'll have time to clear the screen. |
| // This is also the case when the user signs off. |
| if (!shutting_down_) { |
| shutting_down_ = true; |
| HideAndMaybeLockCursor(/*lock=*/true); |
| animator_->StartAnimation(SessionStateAnimator::kAllNonRootContainersMask, |
| SessionStateAnimator::ANIMATION_HIDE_IMMEDIATELY, |
| SessionStateAnimator::ANIMATION_SPEED_IMMEDIATE); |
| } |
| } |
| |
| void LockStateController::OnLockStateChanged(bool locked) { |
| // Unpause if lock animations didn't start and ends in 3 seconds. |
| constexpr base::TimeDelta kPauseTimeout = base::Seconds(3); |
| |
| DCHECK((lock_fail_timer_.IsRunning() && lock_duration_timer_ != nullptr) || |
| (!lock_fail_timer_.IsRunning() && lock_duration_timer_ == nullptr)); |
| VLOG(1) << "OnLockStateChanged called with locked: " << locked |
| << ", shutting_down_: " << shutting_down_ |
| << ", system_is_locked_: " << system_is_locked_ |
| << ", lock_fail_timer_.IsRunning(): " << lock_fail_timer_.IsRunning() |
| << ", animating_unlock_: " << static_cast<int>(animating_unlock_) |
| << ", animating_lock_: " << static_cast<int>(animating_lock_); |
| |
| if (shutting_down_ || (system_is_locked_ == locked)) |
| return; |
| |
| system_is_locked_ = locked; |
| |
| Shell::Get()->occlusion_tracker_pauser()->PauseUntilAnimationsEnd( |
| kPauseTimeout); |
| |
| if (locked) { |
| StartPostLockAnimation(); |
| |
| lock_fail_timer_.Stop(); |
| |
| if (lock_duration_timer_) { |
| UMA_HISTOGRAM_LOCK_TIMES("Ash.WindowManager.Lock.Success", |
| lock_duration_timer_->Elapsed()); |
| lock_duration_timer_.reset(); |
| } |
| } else { |
| StartUnlockAnimationAfterLockUIDestroyed(); |
| } |
| } |
| |
| void LockStateController::CancelUnlockAnimation() { |
| VLOG(1) << "CancelUnlockAnimation"; |
| pb_pressed_during_unlock_ = true; |
| } |
| |
| void LockStateController::OnLockFailTimeout() { |
| UMA_HISTOGRAM_LOCK_TIMES("Ash.WindowManager.Lock.Timeout", |
| lock_duration_timer_->Elapsed()); |
| lock_duration_timer_.reset(); |
| DCHECK(!system_is_locked_); |
| |
| // b/228873153: Here we use `LOG(ERROR)` instead of `LOG(FATAL)` because it |
| // seems like certain users are hitting this timeout causing chrome to crash |
| // and be restarted from session manager without `--login-manager` |
| LOG(ERROR) << "Screen lock took too long; Signing out"; |
| base::debug::DumpWithoutCrashing(); |
| Shell::Get()->session_controller()->RequestSignOut(); |
| } |
| |
| void LockStateController::PreLockAnimation( |
| SessionStateAnimator::AnimationSpeed speed, |
| bool request_lock_on_completion) { |
| VLOG(1) << "PreLockAnimation"; |
| saved_blur_ = Shell::GetPrimaryRootWindowController() |
| ->wallpaper_widget_controller() |
| ->GetWallpaperBlur(); |
| Shell::Get()->wallpaper_controller()->UpdateWallpaperBlurForLockState(true); |
| auto next_animation_starter = base::BindOnce( |
| &LockStateController::PreLockAnimationFinished, |
| weak_ptr_factory_.GetWeakPtr(), request_lock_on_completion); |
| SessionStateAnimator::AnimationSequence* animation_sequence = |
| animator_->BeginAnimationSequence(std::move(next_animation_starter)); |
| |
| animation_sequence->StartAnimation( |
| SessionStateAnimator::NON_LOCK_SCREEN_CONTAINERS, |
| SessionStateAnimator::ANIMATION_LIFT, speed); |
| animation_sequence->StartAnimation(SessionStateAnimator::SHELF, |
| SessionStateAnimator::ANIMATION_FADE_OUT, |
| speed); |
| // Hide the screen locker containers so we can raise them later. |
| animator_->StartAnimation(SessionStateAnimator::LOCK_SCREEN_CONTAINERS, |
| SessionStateAnimator::ANIMATION_HIDE_IMMEDIATELY, |
| SessionStateAnimator::ANIMATION_SPEED_IMMEDIATE); |
| AnimateWallpaperAppearanceIfNecessary(speed, animation_sequence); |
| |
| animation_sequence->EndSequence(); |
| } |
| |
| void LockStateController::StartPostLockAnimation() { |
| VLOG(1) << "StartPostLockAnimation"; |
| auto next_animation_starter = |
| base::BindOnce(&LockStateController::PostLockAnimationFinished, |
| weak_ptr_factory_.GetWeakPtr()); |
| SessionStateAnimator::AnimationSequence* animation_sequence = |
| animator_->BeginAnimationSequence(std::move(next_animation_starter)); |
| |
| animation_sequence->StartAnimation( |
| SessionStateAnimator::LOCK_SCREEN_CONTAINERS, |
| SessionStateAnimator::ANIMATION_RAISE_TO_SCREEN, |
| post_lock_immediate_animation_ |
| ? SessionStateAnimator::ANIMATION_SPEED_IMMEDIATE |
| : SessionStateAnimator::ANIMATION_SPEED_MOVE_WINDOWS); |
| // Show the lock screen shelf. This is a no-op if views-based shelf is |
| // disabled, since shelf is in NonLockScreenContainersContainer. |
| animation_sequence->StartAnimation( |
| SessionStateAnimator::SHELF, SessionStateAnimator::ANIMATION_FADE_IN, |
| post_lock_immediate_animation_ |
| ? SessionStateAnimator::ANIMATION_SPEED_IMMEDIATE |
| : SessionStateAnimator::ANIMATION_SPEED_MOVE_WINDOWS); |
| animation_sequence->EndSequence(); |
| post_lock_fail_timer_.Start(FROM_HERE, kPostLockFailTimeout, this, |
| &LockStateController::OnPostLockFailTimeout); |
| } |
| |
| void LockStateController::StartUnlockAnimationBeforeLockUIDestroyed( |
| base::OnceClosure callback) { |
| VLOG(1) << "StartUnlockAnimationBeforeLockUIDestroyed"; |
| animating_unlock_ = true; |
| // Hide the lock screen shelf. This is a no-op if views-based shelf is |
| // disabled, since shelf is in NonLockScreenContainersContainer. |
| animator_->StartAnimation(SessionStateAnimator::SHELF, |
| SessionStateAnimator::ANIMATION_FADE_OUT, |
| SessionStateAnimator::ANIMATION_SPEED_MOVE_WINDOWS); |
| animator_->StartAnimationWithCallback( |
| SessionStateAnimator::LOCK_SCREEN_CONTAINERS, |
| SessionStateAnimator::ANIMATION_LIFT, |
| SessionStateAnimator::ANIMATION_SPEED_MOVE_WINDOWS, std::move(callback)); |
| animator_->StartAnimation(SessionStateAnimator::NON_LOCK_SCREEN_CONTAINERS, |
| SessionStateAnimator::ANIMATION_COPY_LAYER, |
| SessionStateAnimator::ANIMATION_SPEED_IMMEDIATE); |
| } |
| |
| void LockStateController::StartUnlockAnimationAfterLockUIDestroyed() { |
| VLOG(1) << "StartUnlockAnimationAfterLockUIDestroyed"; |
| auto next_animation_starter = base::BindOnce( |
| &LockStateController::UnlockAnimationAfterLockUIDestroyedFinished, |
| weak_ptr_factory_.GetWeakPtr()); |
| SessionStateAnimator::AnimationSequence* animation_sequence = |
| animator_->BeginAnimationSequence(std::move(next_animation_starter)); |
| |
| animation_sequence->StartAnimation( |
| SessionStateAnimator::NON_LOCK_SCREEN_CONTAINERS, |
| SessionStateAnimator::ANIMATION_DROP, |
| SessionStateAnimator::ANIMATION_SPEED_MOVE_WINDOWS); |
| animation_sequence->StartAnimation( |
| SessionStateAnimator::SHELF, SessionStateAnimator::ANIMATION_FADE_IN, |
| SessionStateAnimator::ANIMATION_SPEED_MOVE_WINDOWS); |
| AnimateWallpaperHidingIfNecessary( |
| SessionStateAnimator::ANIMATION_SPEED_MOVE_WINDOWS, animation_sequence); |
| animation_sequence->EndSequence(); |
| } |
| |
| void LockStateController::LockAnimationCancelled(bool aborted) { |
| VLOG(1) << "LockAnimationCancelled: aborted=" << aborted; |
| RestoreUnlockedProperties(); |
| } |
| |
| void LockStateController::PreLockAnimationFinished(bool request_lock, |
| bool aborted) { |
| VLOG(1) << "PreLockAnimationFinished: aborted=" << aborted; |
| // Aborted in this stage means the locking animation was cancelled by |
| // `CancelLockAnimation()`, triggered by releasing a lock button before |
| // finishing animation. |
| if (aborted) |
| return; |
| |
| // Don't do anything (including starting the lock-fail timer) if the screen |
| // was already locked while the animation was going. |
| if (system_is_locked_) { |
| DCHECK(!request_lock) << "Got request to lock already-locked system " |
| << "at completion of pre-lock animation"; |
| return; |
| } |
| |
| if (request_lock) { |
| base::RecordAction(base::UserMetricsAction("Accel_LockScreen_LockButton")); |
| Shell::Get()->session_controller()->LockScreen(); |
| } |
| |
| VLOG(1) << "b/228873153 : Starting lock fail timer"; |
| lock_fail_timer_.Start(FROM_HERE, kLockFailTimeout, this, |
| &LockStateController::OnLockFailTimeout); |
| |
| lock_duration_timer_ = std::make_unique<base::ElapsedTimer>(); |
| } |
| |
| void LockStateController::OnPostLockFailTimeout() { |
| VLOG(1) << "OnPostLockFailTimeout"; |
| PostLockAnimationFinished(true); |
| } |
| |
| void LockStateController::PostLockAnimationFinished(bool aborted) { |
| VLOG(1) << "PostLockAnimationFinished: aborted=" << aborted; |
| if (!animating_lock_) |
| return; |
| animating_lock_ = false; |
| post_lock_immediate_animation_ = false; |
| post_lock_fail_timer_.Stop(); |
| OnLockStateEvent(LockStateObserver::EVENT_LOCK_ANIMATION_FINISHED); |
| if (!lock_screen_displayed_callback_.is_null()) |
| std::move(lock_screen_displayed_callback_).Run(); |
| } |
| |
| void LockStateController::UnlockAnimationAfterLockUIDestroyedFinished( |
| bool aborted) { |
| VLOG(1) << "UnlockAnimationAfterLockUIDestroyedFinished: aborted=" << aborted; |
| animating_unlock_ = false; |
| if (pb_pressed_during_unlock_) { |
| Shell::Get()->session_controller()->LockScreen(); |
| pb_pressed_during_unlock_ = false; |
| } else { |
| Shell::Get()->wallpaper_controller()->UpdateWallpaperBlurForLockState( |
| false); |
| RestoreUnlockedProperties(); |
| } |
| } |
| |
| void LockStateController::StoreUnlockedProperties() { |
| if (!unlocked_properties_) { |
| unlocked_properties_ = std::make_unique<UnlockedStateProperties>(); |
| unlocked_properties_->wallpaper_is_hidden = animator_->IsWallpaperHidden(); |
| } |
| if (unlocked_properties_->wallpaper_is_hidden) { |
| // Hide wallpaper so that it can be animated later. |
| animator_->StartAnimation(SessionStateAnimator::WALLPAPER, |
| SessionStateAnimator::ANIMATION_HIDE_IMMEDIATELY, |
| SessionStateAnimator::ANIMATION_SPEED_IMMEDIATE); |
| animator_->ShowWallpaper(); |
| } |
| } |
| |
| void LockStateController::RestoreUnlockedProperties() { |
| if (!unlocked_properties_) |
| return; |
| if (unlocked_properties_->wallpaper_is_hidden) { |
| animator_->HideWallpaper(); |
| // Restore wallpaper visibility. |
| animator_->StartAnimation(SessionStateAnimator::WALLPAPER, |
| SessionStateAnimator::ANIMATION_FADE_IN, |
| SessionStateAnimator::ANIMATION_SPEED_IMMEDIATE); |
| } |
| unlocked_properties_.reset(); |
| } |
| |
| void LockStateController::AnimateWallpaperAppearanceIfNecessary( |
| SessionStateAnimator::AnimationSpeed speed, |
| SessionStateAnimator::AnimationSequence* animation_sequence) { |
| if (unlocked_properties_.get() && unlocked_properties_->wallpaper_is_hidden) { |
| animation_sequence->StartAnimation(SessionStateAnimator::WALLPAPER, |
| SessionStateAnimator::ANIMATION_FADE_IN, |
| speed); |
| } |
| } |
| |
| void LockStateController::AnimateWallpaperHidingIfNecessary( |
| SessionStateAnimator::AnimationSpeed speed, |
| SessionStateAnimator::AnimationSequence* animation_sequence) { |
| if (unlocked_properties_.get() && unlocked_properties_->wallpaper_is_hidden) { |
| animation_sequence->StartAnimation(SessionStateAnimator::WALLPAPER, |
| SessionStateAnimator::ANIMATION_FADE_OUT, |
| speed); |
| } |
| } |
| |
| void LockStateController::OnLockStateEvent(LockStateObserver::EventType event) { |
| if (shutting_down_) |
| return; |
| |
| for (auto& observer : observers_) |
| observer.OnLockStateEvent(event); |
| } |
| |
| void LockStateController::StartPreShutdownAnimationTimer() { |
| cancelable_shutdown_timer_.Stop(); |
| cancelable_shutdown_timer_.Start( |
| FROM_HERE, |
| animator_->GetDuration(SessionStateAnimator::ANIMATION_SPEED_SHUTDOWN), |
| this, &LockStateController::OnPreShutdownAnimationTimeout); |
| } |
| |
| void LockStateController::OnPreShutdownAnimationTimeout() { |
| VLOG(1) << "OnPreShutdownAnimationTimeout"; |
| shutting_down_ = true; |
| |
| HideAndMaybeLockCursor(/*lock=*/false); |
| StartSessionStateChangeTimer(/*with_animation_time=*/false, |
| RequestedSessionState::kCancelableShutdown); |
| } |
| |
| void LockStateController::StartSessionStateChangeTimer( |
| bool with_animation_time, |
| RequestedSessionState requested_session_state) { |
| base::TimeDelta duration = kShutdownRequestDelay; |
| if (with_animation_time) { |
| duration += |
| animator_->GetDuration(SessionStateAnimator::ANIMATION_SPEED_SHUTDOWN); |
| } |
| // Play and get shutdown sound duration from chrome in |sound_duration|. And |
| // start real shutdown after a delay of |duration|. |
| if (requested_session_state == RequestedSessionState::kShutdown || |
| requested_session_state == RequestedSessionState::kCancelableShutdown) { |
| base::TimeDelta sound_duration = |
| std::min(Shell::Get()->accessibility_controller()->PlayShutdownSound(), |
| base::Milliseconds(kMaxShutdownSoundDurationMs)); |
| duration = std::max(duration, sound_duration); |
| } |
| session_state_change_timer_.Start( |
| FROM_HERE, duration, |
| base::BindOnce(&LockStateController::OnSessionStateChangeTimeout, |
| base::Unretained(this), requested_session_state)); |
| } |
| |
| void LockStateController::OnSessionStateChangeTimeout( |
| RequestedSessionState requested_session_state) { |
| if (requested_session_state == RequestedSessionState::kShutdown || |
| requested_session_state == RequestedSessionState::kCancelableShutdown) { |
| VLOG(1) << "OnSessionStateChangeTimeout with shutdown requested"; |
| } |
| switch (requested_session_state) { |
| case RequestedSessionState::kShutdown: |
| case RequestedSessionState::kCancelableShutdown: |
| DCHECK(shutting_down_); |
| DCHECK(shutdown_reason_); |
| shutdown_controller_->ShutDownOrReboot(*shutdown_reason_); |
| break; |
| case RequestedSessionState::kRestart: |
| std::move(restart_callback_).Run(); |
| break; |
| case RequestedSessionState::kSignOut: |
| Shell::Get()->session_controller()->RequestSignOut(); |
| break; |
| } |
| } |
| |
| void LockStateController::SessionStateChangeWithInformedRestore( |
| RequestedSessionState requested_session_state) { |
| const base::FilePath file_path = GetInformedRestoreImagePath(); |
| |
| if (!ShouldTakeInformedRestoreScreenshot()) { |
| DeleteInformedRestoreImage(informed_restore_image_callback_for_test_, |
| file_path); |
| StartSessionStateChange(requested_session_state); |
| return; |
| } |
| |
| // Check if there are any content currently on the screen that are restricted |
| // by DLP. |
| CaptureModeController::Get()->CheckScreenCaptureDlpRestrictions( |
| shutting_down_, |
| base::BindOnce( |
| &LockStateController::OnDlpRestrictionCheckedAtScreenCapture, |
| weak_ptr_factory_.GetWeakPtr(), requested_session_state, file_path)); |
| } |
| |
| void LockStateController::OnDlpRestrictionCheckedAtScreenCapture( |
| RequestedSessionState requested_session_state, |
| const base::FilePath& file_path, |
| bool proceed) { |
| if (!proceed) { |
| RecordScreenshotOnShutdownStatus(ScreenshotOnShutdownStatus::kFailedOnDLP); |
| StartSessionStateChange(requested_session_state); |
| return; |
| } |
| |
| // TODO(b/319921650): Finalize the expected behavior on multi-display. |
| auto* root = Shell::GetRootWindowForNewWindows(); |
| |
| // Create a new layer that mirrors the painted wallpaper view layer. Adds it |
| // to be the bottom-most child of the shutdown screenshot container layer, |
| // which is the parent of the active desk container also the container that we |
| // are going to take the informed restore screenshot. With this, |
| // 1) wallpaper will be included in the screenshot besides the content of the |
| // active desk. |
| // 2) screenshot will be taken on the whole desktop instead of the specific |
| // area with windows. This guarantees the windows' relative position inside |
| // the desktop. |
| auto* wallpaper_layer = RootWindowController::ForWindow(root) |
| ->wallpaper_widget_controller() |
| ->wallpaper_view() |
| ->layer(); |
| CHECK(wallpaper_layer && wallpaper_layer->children().empty()); |
| mirror_wallpaper_layer_ = wallpaper_layer->Mirror(); |
| |
| auto* informed_restore_screenshot_container = |
| root->GetChildById(kShellWindowId_ShutdownScreenshotContainer); |
| auto* shutdown_screenshot_layer = |
| informed_restore_screenshot_container->layer(); |
| shutdown_screenshot_layer->Add(mirror_wallpaper_layer_.get()); |
| shutdown_screenshot_layer->StackAtBottom(mirror_wallpaper_layer_.get()); |
| |
| if (!disable_screenshot_timeout_for_test_) { |
| // Trigger the `take_screenshot_fail_timer_` and start taking the screenshot |
| // at the same time. If the timer timeouts before receiving the screenshot, |
| // shutdown process will be triggered without the screenshot. |
| take_screenshot_fail_timer_.Start( |
| FROM_HERE, kTakeScreenshotFailTimeout, |
| base::BindOnce(&LockStateController::OnTakeScreenshotFailTimeout, |
| base::Unretained(this), requested_session_state)); |
| } |
| |
| if (auto* workspace_controller = GetWorkspaceController( |
| desks_util::GetActiveDeskContainerForRoot(root))) { |
| if (BackdropController* backdrop_controller = |
| workspace_controller->layout_manager()->backdrop_controller()) { |
| backdrop_controller->HideOnTakingInformedRestoreScreenshot(); |
| } |
| } |
| |
| // Take the screenshot on the shutdown screenshot container, thus the float |
| // and the always on top windows will be included in the screenshot as well. |
| ui::GrabWindowSnapshot( |
| informed_restore_screenshot_container, |
| /*source_rect=*/ |
| gfx::Rect(informed_restore_screenshot_container->bounds().size()), |
| base::BindOnce(&LockStateController::OnInformedRestoreImageTaken, |
| weak_ptr_factory_.GetWeakPtr(), requested_session_state, |
| file_path, base::TimeTicks::Now())); |
| } |
| |
| void LockStateController::StartSessionStateChange( |
| RequestedSessionState requested_session_state) { |
| if (requested_session_state == RequestedSessionState::kCancelableShutdown && |
| shutdown_canceled_) { |
| return; |
| } |
| |
| animator_->StartAnimation( |
| SessionStateAnimator::ROOT_CONTAINER, |
| SessionStateAnimator::ANIMATION_GRAYSCALE_BRIGHTNESS, |
| SessionStateAnimator::ANIMATION_SPEED_SHUTDOWN); |
| |
| if (requested_session_state == RequestedSessionState::kCancelableShutdown) { |
| StartPreShutdownAnimationTimer(); |
| } else { |
| StartSessionStateChangeTimer(/*with_animation_time=*/true, |
| requested_session_state); |
| } |
| } |
| |
| void LockStateController::OnTakeScreenshotFailTimeout( |
| RequestedSessionState requested_session_state) { |
| SaveInformedRestoreScreenshotDuration( |
| local_state_, prefs::kInformedRestoreScreenshotTakenDuration, |
| kTakeScreenshotFailTimeout); |
| RecordScreenshotOnShutdownStatus( |
| ScreenshotOnShutdownStatus::kFailedOnTakingScreenshotTimeout); |
| mirror_wallpaper_layer_.reset(); |
| DeleteInformedRestoreImage(informed_restore_image_callback_for_test_, |
| GetInformedRestoreImagePath()); |
| StartSessionStateChange(requested_session_state); |
| } |
| |
| void LockStateController::OnInformedRestoreImageTaken( |
| RequestedSessionState requested_session_state, |
| const base::FilePath& file_path, |
| base::TimeTicks start_time, |
| gfx::Image informed_restore_image) { |
| // Do not proceed if the `take_screenshot_fail_timer_` is stopped, which means |
| // taking screenshot process took too long and the shutdown process has been |
| // triggered without the informed restore image. |
| if (!disable_screenshot_timeout_for_test_ && |
| !take_screenshot_fail_timer_.IsRunning()) { |
| return; |
| } |
| |
| take_screenshot_fail_timer_.Stop(); |
| SaveInformedRestoreScreenshotDuration( |
| local_state_, prefs::kInformedRestoreScreenshotTakenDuration, |
| base::TimeTicks::Now() - start_time); |
| |
| mirror_wallpaper_layer_.reset(); |
| |
| base::ThreadPool::PostTaskAndReply( |
| FROM_HERE, |
| {base::MayBlock(), base::TaskPriority::HIGHEST, |
| base::TaskShutdownBehavior::BLOCK_SHUTDOWN}, |
| base::BindOnce(&EncodeAndSaveImage, file_path, |
| std::move(informed_restore_image)), |
| base::BindOnce(&LockStateController::OnInformedRestoreImageSaved, |
| weak_ptr_factory_.GetWeakPtr(), base::TimeTicks::Now(), |
| file_path)); |
| |
| StartSessionStateChange(requested_session_state); |
| } |
| |
| void LockStateController::OnInformedRestoreImageSaved( |
| base::TimeTicks start_time, |
| const base::FilePath& file_path) { |
| SaveInformedRestoreScreenshotDuration( |
| local_state_, prefs::kInformedRestoreScreenshotEncodeAndSaveDuration, |
| // This duration includes the time waiting for the `ThreadPool` to start |
| // running the task, also the time that the UI thread waits to get the |
| // reply from the `ThreadPool`. |
| base::TimeTicks::Now() - start_time); |
| RecordScreenshotOnShutdownStatus(ScreenshotOnShutdownStatus::kSucceeded); |
| if (shutdown_canceled_) { |
| DeleteInformedRestoreImage(informed_restore_image_callback_for_test_, |
| file_path); |
| } |
| if (informed_restore_image_callback_for_test_) { |
| std::move(informed_restore_image_callback_for_test_).Run(); |
| } |
| } |
| |
| void LockStateController::DoRestart(power_manager::RequestRestartReason reason, |
| const std::string& description) { |
| chromeos::PowerManagerClient::Get()->RequestRestart(reason, description); |
| } |
| |
| } // namespace ash |