chromium/ash/ambient/ambient_controller.cc

// Copyright 2019 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/ambient/ambient_controller.h"

#include <algorithm>
#include <memory>
#include <string>
#include <utility>
#include <vector>

#include "ash/ambient/ambient_animation_ui_launcher.h"
#include "ash/ambient/ambient_constants.h"
#include "ash/ambient/ambient_managed_slideshow_ui_launcher.h"
#include "ash/ambient/ambient_photo_cache.h"
#include "ash/ambient/ambient_photo_cache_settings.h"
#include "ash/ambient/ambient_slideshow_ui_launcher.h"
#include "ash/ambient/ambient_ui_launcher.h"
#include "ash/ambient/ambient_ui_settings.h"
#include "ash/ambient/ambient_video_ui_launcher.h"
#include "ash/ambient/managed/screensaver_images_policy_handler.h"
#include "ash/ambient/metrics/ambient_metrics.h"
#include "ash/ambient/metrics/ambient_session_metrics_recorder.h"
#include "ash/ambient/metrics/managed_screensaver_metrics.h"
#include "ash/ambient/model/ambient_animation_photo_config.h"
#include "ash/ambient/model/ambient_photo_config.h"
#include "ash/ambient/model/ambient_slideshow_photo_config.h"
#include "ash/ambient/model/ambient_topic_queue_animation_delegate.h"
#include "ash/ambient/model/ambient_topic_queue_slideshow_delegate.h"
#include "ash/ambient/resources/ambient_animation_static_resources.h"
#include "ash/ambient/ui/ambient_animation_frame_rate_controller.h"
#include "ash/ambient/ui/ambient_animation_progress_tracker.h"
#include "ash/ambient/ui/ambient_container_view.h"
#include "ash/ambient/ui/ambient_view_delegate.h"
#include "ash/ambient/util/ambient_util.h"
#include "ash/assistant/model/assistant_interaction_model.h"
#include "ash/constants/ash_features.h"
#include "ash/login/ui/lock_screen.h"
#include "ash/public/cpp/ambient/ambient_backend_controller.h"
#include "ash/public/cpp/ambient/ambient_client.h"
#include "ash/public/cpp/ambient/ambient_mode_photo_source.h"
#include "ash/public/cpp/ambient/ambient_prefs.h"
#include "ash/public/cpp/ambient/ambient_ui_model.h"
#include "ash/public/cpp/ambient/common/ambient_settings.h"
#include "ash/public/cpp/ambient/fake_ambient_backend_controller_impl.h"
#include "ash/public/cpp/assistant/controller/assistant_interaction_controller.h"
#include "ash/public/cpp/shell_window_ids.h"
#include "ash/root_window_controller.h"
#include "ash/session/session_controller_impl.h"
#include "ash/shell.h"
#include "ash/strings/grit/ash_strings.h"
#include "ash/system/power/power_status.h"
#include "ash/webui/personalization_app/mojom/personalization_app.mojom-shared.h"
#include "base/check.h"
#include "base/check_deref.h"
#include "base/feature_list.h"
#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/logging.h"
#include "base/memory/weak_ptr.h"
#include "base/metrics/user_metrics.h"
#include "base/notreached.h"
#include "base/path_service.h"
#include "base/task/thread_pool.h"
#include "base/time/time.h"
#include "base/timer/timer.h"
#include "base/values.h"
#include "build/buildflag.h"
#include "cc/paint/skottie_wrapper.h"
#include "chromeos/ash/components/assistant/buildflags.h"
#include "chromeos/ash/services/assistant/public/cpp/assistant_service.h"
#include "chromeos/components/kiosk/kiosk_utils.h"
#include "chromeos/dbus/power/power_manager_client.h"
#include "chromeos/dbus/power_manager/backlight.pb.h"
#include "chromeos/dbus/power_manager/idle.pb.h"
#include "components/prefs/pref_change_registrar.h"
#include "components/prefs/pref_registry_simple.h"
#include "components/prefs/pref_service.h"
#include "ui/aura/client/aura_constants.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/ui_base_types.h"
#include "ui/base/user_activity/user_activity_detector.h"
#include "ui/views/accessibility/view_accessibility.h"
#include "ui/views/view.h"
#include "ui/views/widget/widget.h"
#include "ui/views/widget/widget_delegate.h"
#include "ui/wm/core/cursor_manager.h"
#include "ui/wm/core/visibility_controller.h"
#include "ui/wm/core/window_animations.h"

#if BUILDFLAG(ENABLE_CROS_AMBIENT_MODE_BACKEND)
#include "ash/ambient/backdrop/ambient_backend_controller_impl.h"
#endif  // BUILDFLAG(ENABLE_CROS_AMBIENT_MODE_BACKEND)

namespace ash {

namespace {

// Used by wake lock APIs.
constexpr char kWakeLockReason[] = "AmbientMode";

// kAmbientModeRunningDurationMinutes with value 0 means "forever".
constexpr int kDurationForever = 0;

std::unique_ptr<AmbientBackendController> CreateAmbientBackendController() {
#if BUILDFLAG(ENABLE_CROS_AMBIENT_MODE_BACKEND)
  return std::make_unique<AmbientBackendControllerImpl>();
#else
  return std::make_unique<FakeAmbientBackendControllerImpl>();
#endif  // BUILDFLAG(ENABLE_CROS_AMBIENT_MODE_BACKEND)
}

// Returns the name of the ambient widget.
std::string GetWidgetName() {
  if (ambient::util::IsShowing(LockScreen::ScreenType::kLock))
    return "LockScreenAmbientModeContainer";
  return "InSessionAmbientModeContainer";
}

// Returns true if the device is currently connected to a charger.
bool IsChargerConnected() {
  DCHECK(PowerStatus::IsInitialized());
  auto* power_status = PowerStatus::Get();
  if (power_status->IsBatteryPresent()) {
    // If battery is charging, that implies sufficient power is connected. If
    // battery is not charging, return true only if an official, non-USB charger
    // is connected. This will happen if the battery is fully charged or
    // charging is delayed by Adaptive Charging.
    return power_status->IsBatteryCharging() ||
           power_status->IsMainsChargerConnected();
  } else {
    // Chromeboxes have no battery.
    return power_status->IsLinePowerConnected();
  }
}

bool IsUiHidden(AmbientUiVisibility visibility) {
  return visibility == AmbientUiVisibility::kHidden;
}

PrefService* GetPrimaryUserPrefService() {
  return Shell::Get()->session_controller()->GetPrimaryUserPrefService();
}

PrefService* GetSigninPrefService() {
  return Shell::Get()->session_controller()->GetSigninScreenPrefService();
}

PrefService* GetActivePrefService() {
  if (GetPrimaryUserPrefService()) {
    return GetPrimaryUserPrefService();
  }
  if (ash::features::IsAmbientModeManagedScreensaverEnabled()) {
    return GetSigninPrefService();
  }
  return nullptr;
}

bool IsUserAmbientModeEnabled() {
  if (!AmbientClient::Get()->IsAmbientModeAllowed()) {
    return false;
  }

  auto* pref_service = GetPrimaryUserPrefService();
  return pref_service &&
         pref_service->GetBoolean(ambient::prefs::kAmbientModeEnabled);
}

bool IsAmbientModeManagedScreensaverEnabled() {
  PrefService* pref_service = GetActivePrefService();

  return ash::features::IsAmbientModeManagedScreensaverEnabled() &&
         !chromeos::IsKioskSession() && pref_service &&
         pref_service->GetBoolean(
             ambient::prefs::kAmbientModeManagedScreensaverEnabled);
}

bool IsAmbientModeEnabled() {
  return IsUserAmbientModeEnabled() || IsAmbientModeManagedScreensaverEnabled();
}

class AmbientWidgetDelegate : public views::WidgetDelegate {
 public:
  AmbientWidgetDelegate() {
    SetCanFullscreen(true);
    SetCanMaximize(true);
    SetOwnedByWidget(true);
  }
};

void RecordManagedScreensaverEnabledPref() {
  if (!ash::features::IsAmbientModeManagedScreensaverEnabled()) {
    return;
  }

  if (PrefService* pref_service = GetActivePrefService();
      pref_service &&
      pref_service->IsManagedPreference(
          ambient::prefs::kAmbientModeManagedScreensaverEnabled)) {
    RecordManagedScreensaverEnabled(pref_service->GetBoolean(
        ambient::prefs::kAmbientModeManagedScreensaverEnabled));
  }
}

}  // namespace

// static
void AmbientController::RegisterProfilePrefs(PrefRegistrySimple* registry) {
  registry->RegisterStringPref(ash::ambient::prefs::kAmbientBackdropClientId,
                               std::string());

  // Do not sync across devices to allow different usages for different
  // devices.
  registry->RegisterBooleanPref(ash::ambient::prefs::kAmbientModeEnabled,
                                false);

  // Used to upload usage metrics. Derived from |AmbientSettings| when
  // settings are successfully saved by the user. This pref is not displayed
  // to the user.
  registry->RegisterIntegerPref(
      ash::ambient::prefs::kAmbientModePhotoSourcePref,
      static_cast<int>(ash::ambient::AmbientModePhotoSource::kUnset));

  // Used to control the number of seconds of inactivity on lock screen before
  // showing Ambient mode. This pref is not displayed to the user. Registered
  // as integer rather than TimeDelta to work with prefs_util.
  registry->RegisterIntegerPref(
      ambient::prefs::kAmbientModeLockScreenInactivityTimeoutSeconds,
      kLockScreenInactivityTimeout.InSeconds());

  // Used to control the number of seconds to lock the session after starting
  // Ambient mode. This pref is not displayed to the user. Registered as
  // integer rather than TimeDelta to work with prefs_util.
  registry->RegisterIntegerPref(
      ambient::prefs::kAmbientModeLockScreenBackgroundTimeoutSeconds,
      kLockScreenBackgroundTimeout.InSeconds());

  // Used to control the photo refresh interval in Ambient mode. This pref is
  // not displayed to the user. Registered as integer rather than TimeDelta to
  // work with prefs_util.
  registry->RegisterIntegerPref(
      ambient::prefs::kAmbientModePhotoRefreshIntervalSeconds,
      kPhotoRefreshInterval.InSeconds());

  // |ambient::prefs::kAmbientTheme| is for legacy purposes only. It is being
  // migrated to |ambient::prefs::kAmbientUiSettings|, which is the newer
  // version of these settings.
  registry->RegisterIntegerPref(ambient::prefs::kAmbientTheme,
                                static_cast<int>(kDefaultAmbientTheme));
  registry->RegisterDictionaryPref(ambient::prefs::kAmbientUiSettings);

  registry->RegisterDoublePref(
      ambient::prefs::kAmbientModeAnimationPlaybackSpeed,
      kAnimationPlaybackSpeed);

  registry->RegisterBooleanPref(
      ash::ambient::prefs::kAmbientModeManagedScreensaverEnabled, false);

  registry->RegisterIntegerPref(
      ambient::prefs::kAmbientModeManagedScreensaverIdleTimeoutSeconds,
      kManagedScreensaverInactivityTimeout.InSeconds());

  registry->RegisterIntegerPref(
      ambient::prefs::kAmbientModeManagedScreensaverImageDisplayIntervalSeconds,
      kManagedScreensaverImageRefreshInterval.InSeconds());

  registry->RegisterIntegerPref(
      ambient::prefs::kAmbientModeRunningDurationMinutes, kDurationForever);
}

AmbientController::AmbientController(
    mojo::PendingRemote<device::mojom::Fingerprint> fingerprint)
    : ambient_weather_controller_(std::make_unique<AmbientWeatherController>(
          SimpleGeolocationProvider::GetInstance())),
      fingerprint_(std::move(fingerprint)) {
  ambient_photo_cache::SetFileTaskRunner(
      base::ThreadPool::CreateSequencedTaskRunner(
          {base::MayBlock(), base::TaskPriority::BEST_EFFORT,
           base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN}));
  ambient_backend_controller_ = CreateAmbientBackendController();

  // |SessionController| is initialized before |this| in Shell. Necessary to
  // bind observer here to monitor |OnActiveUserPrefServiceChanged|.
  session_observer_.Observe(Shell::Get()->session_controller());
  backlights_forced_off_observation_.Observe(
      Shell::Get()->backlights_forced_off_setter());
}

AmbientController::~AmbientController() {
  SetUiVisibilityClosed(/*immediately=*/true);
}

void AmbientController::OnAmbientUiVisibilityChanged(
    AmbientUiVisibility visibility) {
  switch (visibility) {
    case AmbientUiVisibility::kShouldShow:
      // Cancels the timer upon shown.
      inactivity_timer_.Stop();

      if (IsChargerConnected()) {
        // Requires wake lock to prevent display from sleeping.
        AcquireWakeLock();
        StartTimerToReleaseWakeLock();
      }
      // Observes the |PowerStatus| on the battery charging status change for
      // the current ambient session.
      if (!power_status_observer_.IsObserving())
        power_status_observer_.Observe(PowerStatus::Get());

      MaybeStartScreenSaver();
      break;
    case AmbientUiVisibility::kPreview: {
      MaybeStartScreenSaver();
      break;
    }
    case AmbientUiVisibility::kHidden:
    case AmbientUiVisibility::kClosed: {
      // TODO(wutao): This will clear the image cache currently. It will not
      // work with `kHidden` if the token has expired and ambient mode is shown
      // again.
      StopScreensaver();

      // Should do nothing if the wake lock has already been released.
      ReleaseWakeLock();

      ClearPreTargetHandler();

      // Should stop observing AssistantInteractionModel when ambient screen is
      // not shown.
      AssistantInteractionController::Get()->GetModel()->RemoveObserver(this);

      if (visibility == AmbientUiVisibility::kHidden) {
        if (LockScreen::HasInstance()) {
          // Add observer for user activity.
          if (!user_activity_observer_.IsObserving())
            user_activity_observer_.Observe(ui::UserActivityDetector::Get());

          // Start timer to show ambient mode.
          inactivity_timer_.Start(
              FROM_HERE, ambient_ui_model_.lock_screen_inactivity_timeout(),
              base::BindOnce(&AmbientController::OnAutoShowTimeOut,
                             weak_ptr_factory_.GetWeakPtr()));
        }
      } else {
        DCHECK(visibility == AmbientUiVisibility::kClosed);
        inactivity_timer_.Stop();
        user_activity_observer_.Reset();
        power_status_observer_.Reset();
      }

      break;
    }
  }
}

void AmbientController::OnAutoShowTimeOut() {
  DCHECK(IsUiHidden(ambient_ui_model_.ui_visibility()));

  // Show ambient screen after time out.
  SetUiVisibilityShouldShow();
}

void AmbientController::OnLoginOrLockScreenCreated() {
  if (!ambient::util::IsShowing(LockScreen::ScreenType::kLogin)) {
    return;
  }
  OnLoginLockStateChanged(LockScreenState::kLogin);
}

void AmbientController::OnLockStateChanged(bool locked) {
  OnLoginLockStateChanged(locked ? LockScreenState::kLocked
                                 : LockScreenState::kUnlocked);
}

void AmbientController::OnLoginLockStateChanged(LockScreenState state) {
  if (state == LockScreenState::kUnlocked) {
    // Ambient screen will be destroyed along with the lock screen when user
    // logs in.
    SetUiVisibilityClosed();
    return;
  }

  if (!IsAmbientModeEnabled()) {
    VLOG(1) << "Ambient mode is not allowed.";
    return;
  }

  // Reset image failures to allow retrying ambient mode after lock state
  // changes.
  if (GetAmbientBackendModel()) {
    GetAmbientBackendModel()->ResetImageFailures();
  }

  // We have 3 options to manage the token for lock screen. Here use option 1.
  // 1. Request only one time after entering lock screen. We will use it once
  //    to request all the image links and no more requests.
  // 2. Request one time before entering lock screen. This will introduce
  //    extra latency.
  // 3. Request and refresh the token in the background (even the ambient mode
  //    is not started) with extra buffer time to use. When entering
  //    lock screen, it will be most likely to have the token already and
  //    enough time to use. More specifically,
  //    3a. We will leave enough buffer time (e.g. 10 mins before expire) to
  //        start to refresh the token.
  //    3b. When lock screen is triggered, most likely we will have >10 mins
  //        of token which can be used on lock screen.
  //    3c. There is a corner case that we may not have the token fetched when
  //        locking screen, we probably can use PrepareForLock(callback) when
  //        locking screen. We can add the refresh token into it. If the token
  //        has already been fetched, then there is not additional time to
  //        wait.
  RequestAccessToken(base::DoNothing(), /*may_refresh_token_on_lock=*/true);

  if (!IsShowing()) {
    // When lock screen starts, we don't immediately show the UI. The Ui is
    // hidden and will show after a delay.
    SetUiVisibilityHidden();
  }
}

AmbientController::LockScreenState AmbientController::GetLockScreenState() {
  if (!LockScreen::HasInstance()) {
    return LockScreenState::kUnlocked;
  }
  if (ambient::util::IsShowing(LockScreen::ScreenType::kLogin)) {
    return LockScreenState::kLogin;
  }
  return LockScreenState::kLocked;
}

void AmbientController::OnActiveUserPrefServiceChanged(
    PrefService* pref_service) {
  if (GetPrimaryUserPrefService() != pref_service) {
    return;
  }

  // Do not continue if pref_change_registrar has already been set up. This
  // prevents re-binding observers when secondary profiles are activated.
  if (pref_change_registrar_)
    return;

  // Once logged in just remove the sign in pref registrations. So that we
  // don't react to device policy changes. Note: we do not need to re-add it
  // on logout because the chrome process is destroyed on logout.
  if (sign_in_pref_change_registrar_) {
    sign_in_pref_change_registrar_.reset();
  }

  bool ambient_mode_allowed = AmbientClient::Get()->IsAmbientModeAllowed();
  bool managed_screensaver_flag_enabled =
      ash::features::IsAmbientModeManagedScreensaverEnabled();

  if (ambient_mode_allowed || managed_screensaver_flag_enabled) {
    pref_change_registrar_ = std::make_unique<PrefChangeRegistrar>();
    pref_change_registrar_->Init(pref_service);
  }

  if (ambient_mode_allowed) {
    pref_change_registrar_->Add(
        ambient::prefs::kAmbientModeEnabled,
        base::BindRepeating(&AmbientController::OnEnabledPrefChanged,
                            weak_ptr_factory_.GetWeakPtr()));
  }

  if (managed_screensaver_flag_enabled) {
    screensaver_images_policy_handler_ =
        ScreensaverImagesPolicyHandler::Create(pref_service);

    pref_change_registrar_->Add(
        ambient::prefs::kAmbientModeManagedScreensaverEnabled,
        base::BindRepeating(&AmbientController::OnEnabledPrefChanged,
                            weak_ptr_factory_.GetWeakPtr()));
  }

  if (ambient_mode_allowed || managed_screensaver_flag_enabled) {
    OnEnabledPrefChanged();
  }
}

void AmbientController::OnSigninScreenPrefServiceInitialized(
    PrefService* pref_service) {
  if (!ash::features::IsAmbientModeManagedScreensaverEnabled()) {
    return;
  }

  screensaver_images_policy_handler_ =
      ScreensaverImagesPolicyHandler::Create(pref_service);

  CHECK(!sign_in_pref_change_registrar_);
  CHECK(!pref_change_registrar_);
  sign_in_pref_change_registrar_ = std::make_unique<PrefChangeRegistrar>();
  sign_in_pref_change_registrar_->Init(pref_service);

  sign_in_pref_change_registrar_->Add(
      ambient::prefs::kAmbientModeManagedScreensaverEnabled,
      base::BindRepeating(&AmbientController::OnEnabledPrefChanged,
                          weak_ptr_factory_.GetWeakPtr()));

  OnEnabledPrefChanged();
}

void AmbientController::OnPowerStatusChanged() {
  if (ambient_ui_model_.ui_visibility() != AmbientUiVisibility::kShouldShow) {
    // No action needed if ambient screen is not shown.
    return;
  }

  // TODO(b/300158227): There is a pending decision of whether we should
  // reacquire wake lock when the power is reconnected before screen saver
  // goes off. We make this change only to make sure that wake lock should
  // never be acquired while on battery.
  if (!IsChargerConnected()) {
    ReleaseWakeLock();
  }
}

void AmbientController::ScreenIdleStateChanged(
    const power_manager::ScreenIdleState& idle_state) {
  DVLOG(1) << "ScreenIdleStateChanged: dimmed(" << idle_state.dimmed()
           << ") off(" << idle_state.off() << ")";

  if (!IsAmbientModeEnabled())
    return;

  is_screen_off_ = idle_state.off();

  if (idle_state.off()) {
    DVLOG(1) << "Screen is off, close ambient mode.";

    SetUiVisibilityClosed(/*immediately=*/true);
    return;
  }

  if (idle_state.dimmed()) {
    // Do not show the UI if lockscreen is active. The inactivity monitor should
    // have activated ambient mode.
    if (LockScreen::HasInstance())
      return;

    // Do not show UI if loading images was unsuccessful.
    if (GetAmbientBackendModel() &&
        GetAmbientBackendModel()->ImageLoadingFailed()) {
      VLOG(1) << "Skipping ambient mode activation due to prior failure";
      GetAmbientBackendModel()->ResetImageFailures();
      return;
    }

    SetUiVisibilityShouldShow();
    return;
  }

  if (LockScreen::HasInstance() &&
      ambient_ui_model_.ui_visibility() == AmbientUiVisibility::kClosed) {
    // Restart hidden ui if the screen is back on and lockscreen is shown.
    SetUiVisibilityHidden();
  }
}

void AmbientController::OnBacklightsForcedOffChanged(bool forced_off) {
  if (forced_off) {
    SetUiVisibilityClosed(/*immediately=*/true);
  }
  if (!forced_off && LockScreen::HasInstance() &&
      ambient_ui_model_.ui_visibility() == AmbientUiVisibility::kClosed) {
    // Restart hidden ui if the screen is back on and lockscreen is shown.
    SetUiVisibilityHidden();
  }
}

void AmbientController::SuspendImminent(
    power_manager::SuspendImminent::Reason reason) {
  // If about to suspend, turn everything off. This covers:
  //   1. Clicking power button.
  //   2. Close lid.
  // Need to specially close the widget immediately here to be able to close
  // the UI before device goes to suspend. Otherwise when opening lid after
  // lid closed, there may be a flash of the old window before previous
  // closing finished.
  SetUiVisibilityClosed(/*immediately=*/true);
  is_suspend_imminent_ = true;
}

void AmbientController::SuspendDone(base::TimeDelta sleep_duration) {
  is_suspend_imminent_ = false;
  // |DismissUI| will restart the lock screen timer if lock screen is active and
  // if Ambient mode is enabled, so call it when resuming from suspend to
  // restart Ambient mode if applicable.
  DismissUI();
}

void AmbientController::OnAuthScanDone(
    const device::mojom::FingerprintMessagePtr msg,
    const base::flat_map<std::string, std::vector<std::string>>& matches) {
  DismissUI();
}

void AmbientController::OnUserActivity(const ui::Event* event) {
  // The following events are handled separately so that we can consume them.
  // In case events come from external sources (i.e. Chrome extensions), the
  // event will be nullptr.
  if (is_receiving_pretarget_events_ && event &&
      (event->IsMouseEvent() || event->IsTouchEvent() || event->IsKeyEvent() ||
       event->IsFlingScrollEvent())) {
    return;
  }
  // While |kPreview| is loading, don't |DismissUI| on user activity.
  // Users can still |DismissUI| with mouse, touch, key or assistant events.
  if (ambient_ui_model_.ui_visibility() == AmbientUiVisibility::kPreview &&
      !IsShowing()) {
    return;
  }
  DismissUI();
}

void AmbientController::OnKeyEvent(ui::KeyEvent* event) {
  // Prevent dispatching key press event to the login UI.
  MaybeStopUiEventPropagation(event);
  // |DismissUI| only on |EventType::kKeyPressed|. Otherwise it won't be
  // possible to start the preview by pressing "enter" key. It'll be cancelled
  // immediately on |EventType::kKeyReleased|.
  if (event->type() == ui::EventType::kKeyPressed) {
    DismissUI();
  }
}

void AmbientController::OnMouseEvent(ui::MouseEvent* event) {
  // |DismissUI| on actual mouse move only if the screen saver widget is shown
  // (images are downloaded).
  if (event->type() == ui::EventType::kMouseMoved) {
    MaybeDismissUIOnMouseMove();
    last_mouse_event_was_move_ = true;
    return;
  }

  // Prevent dispatching mouse event to the windows behind screen saver.
  // Let move event pass through, so that it clears hover states.
  MaybeStopUiEventPropagation(event);
  if (event->IsAnyButton()) {
    DismissUI();
  }
  last_mouse_event_was_move_ = false;
}

void AmbientController::OnTouchEvent(ui::TouchEvent* event) {
  // Prevent dispatching touch event to the windows behind screen saver.
  MaybeStopUiEventPropagation(event);
  DismissUI();
}

void AmbientController::OnInteractionStateChanged(
    InteractionState interaction_state) {
  if (interaction_state == InteractionState::kActive) {
    // Assistant is active.
    DismissUI();
  }
}

void AmbientController::SetUiVisibilityShouldShow() {
  DVLOG(1) << __func__;

  // TODO(meilinw): move the eligibility check to the idle entry point once
  // implemented: b/149246117.
  if (!IsAmbientModeEnabled()) {
    LOG(WARNING) << "Ambient mode is not allowed.";
    return;
  }

  if (is_suspend_imminent_) {
    VLOG(1) << "Do not show UI when suspend imminent";
    return;
  }

  // If the ambient ui launcher is not ready to be started then do not change
  // the visibility. This will disabled the ui launcher until the next AmbientUi
  // starting event occurs. Right now the only ambient ui starting events are
  // screen lock/unlock, screen dim, preview and screen backlight off.
  if (ambient_ui_launcher_ && !ambient_ui_launcher_->IsReady()) {
    return;
  }

  ambient_ui_model_.SetUiVisibility(AmbientUiVisibility::kShouldShow);
}

void AmbientController::SetUiVisibilityPreview() {
  if (!IsAmbientModeEnabled()) {
    LOG(WARNING) << "Ambient mode is not allowed.";
    return;
  }

  ambient_ui_model_.SetUiVisibility(AmbientUiVisibility::kPreview);
  base::RecordAction(base::UserMetricsAction(kScreenSaverPreviewUserAction));
}

void AmbientController::SetUiVisibilityHidden() {
  DVLOG(1) << __func__;

  if (!IsAmbientModeEnabled()) {
    LOG(WARNING) << "Ambient mode is not allowed.";
    return;
  }

  if (is_suspend_imminent_) {
    VLOG(1) << "Do not start hidden UI when suspend imminent";
    return;
  }

  if (is_screen_off_) {
    VLOG(1) << "Do not start hidden UI when screen is off";
    return;
  }

  ambient_ui_model_.SetUiVisibility(AmbientUiVisibility::kHidden);
}

void AmbientController::SetUiVisibilityClosed(bool immediately) {
  DVLOG(1) << __func__;
  // Early return if the UI is already closed to make sure we do not change the
  // cursor visibility when it is not required.
  if (ambient_ui_model_.ui_visibility() == AmbientUiVisibility::kClosed) {
    return;
  }

  close_widgets_immediately_ = immediately;
  ambient_ui_model_.SetUiVisibility(AmbientUiVisibility::kClosed);
  if (!Shell::Get()->IsInTabletMode()) {
    Shell::Get()->cursor_manager()->ShowCursor();
  }
}

void AmbientController::SetScreenSaverDuration(int minutes) {
  auto* pref_service = GetPrimaryUserPrefService();
  if (!pref_service) {
    return;
  }
  pref_service->Set(ambient::prefs::kAmbientModeRunningDurationMinutes,
                    base::Value(minutes));
}

void AmbientController::StartTimerToReleaseWakeLock() {
  CHECK(!screensaver_running_timer_.IsRunning());

  auto* pref_service = GetPrimaryUserPrefService();
  if (!pref_service) {
    return;
  }

  const int session_duration_in_minutes = pref_service->GetInteger(
      ambient::prefs::kAmbientModeRunningDurationMinutes);
  CHECK(session_duration_in_minutes >= 0);

  if (session_duration_in_minutes != kDurationForever) {
    const base::TimeDelta delay = base::Minutes(session_duration_in_minutes);
    screensaver_running_timer_.Start(FROM_HERE, delay, this,
                                     &AmbientController::ReleaseWakeLock);
  }
}

bool AmbientController::ShouldShowAmbientUi() const {
  return ambient_ui_model_.ui_visibility() ==
             AmbientUiVisibility::kShouldShow ||
         ambient_ui_model_.ui_visibility() == AmbientUiVisibility::kPreview;
}

bool AmbientController::IsShowing() const {
  const std::vector<RootWindowController*> root_window_controllers =
      RootWindowController::root_window_controllers();

  const bool has_at_least_one_widget = std::any_of(
      root_window_controllers.cbegin(), root_window_controllers.cend(),
      [](const RootWindowController* const controller) {
        return controller->HasAmbientWidget();
      });

#if DCHECK_IS_ON()
  if (!ShouldShowAmbientUi()) {
    DCHECK(!has_at_least_one_widget);
  }
#endif  // DCHECK_IS_ON()

  return has_at_least_one_widget;
}

void AmbientController::AcquireWakeLock() {
  if (!wake_lock_) {
    mojo::Remote<device::mojom::WakeLockProvider> provider;
    AmbientClient::Get()->RequestWakeLockProvider(
        provider.BindNewPipeAndPassReceiver());
    provider->GetWakeLockWithoutContext(
        device::mojom::WakeLockType::kPreventDisplaySleep,
        device::mojom::WakeLockReason::kOther, kWakeLockReason,
        wake_lock_.BindNewPipeAndPassReceiver());
  }

  DCHECK(wake_lock_);
  wake_lock_->RequestWakeLock();
  VLOG(1) << "Acquired wake lock";

  auto* session_controller = Shell::Get()->session_controller();
  if (session_controller->CanLockScreen() &&
      session_controller->ShouldLockScreenAutomatically()) {
    if (!session_controller->IsScreenLocked() &&
        !delayed_lock_timer_.IsRunning()) {
      delayed_lock_timer_.Start(
          FROM_HERE, ambient_ui_model_.background_lock_screen_timeout(),
          base::BindOnce(
              []() { Shell::Get()->session_controller()->LockScreen(); }));
    }
  }
}

void AmbientController::ReleaseWakeLock() {
  if (!wake_lock_)
    return;

  wake_lock_->CancelWakeLock();
  VLOG(1) << "Released wake lock";

  delayed_lock_timer_.Stop();
  screensaver_running_timer_.Stop();
}

void AmbientController::CloseAllWidgets(bool immediately) {
  for (auto* root_window_controller :
       RootWindowController::root_window_controllers()) {
    root_window_controller->CloseAmbientWidget(immediately);
  }
}

void AmbientController::SetUpPreTargetHandler() {
  if (!is_receiving_pretarget_events_) {
    Shell::Get()->AddPreTargetHandler(this);
    is_receiving_pretarget_events_ = true;
  }
}

void AmbientController::ClearPreTargetHandler() {
  if (is_receiving_pretarget_events_) {
    Shell::Get()->RemovePreTargetHandler(this);
    is_receiving_pretarget_events_ = false;
  }
}

PrefChangeRegistrar* AmbientController::GetActivePrefChangeRegistrar() {
  if (pref_change_registrar_) {
    return pref_change_registrar_.get();
  }

  if (ash::features::IsAmbientModeManagedScreensaverEnabled()) {
    return sign_in_pref_change_registrar_.get();
  }
  return nullptr;
}

void AmbientController::AddManagedScreensaverPolicyPrefObservers() {
  PrefChangeRegistrar* registrar = GetActivePrefChangeRegistrar();
  CHECK(registrar);
  registrar->Add(
      ambient::prefs::kAmbientModeManagedScreensaverIdleTimeoutSeconds,
      base::BindRepeating(
          &AmbientController::
              OnManagedScreensaverLockScreenIdleTimeoutPrefChanged,
          weak_ptr_factory_.GetWeakPtr()));

  registrar->Add(
      ambient::prefs::kAmbientModeManagedScreensaverImageDisplayIntervalSeconds,
      base::BindRepeating(
          &AmbientController::
              OnManagedScreensaverPhotoRefreshIntervalPrefChanged,
          weak_ptr_factory_.GetWeakPtr()));

  OnManagedScreensaverLockScreenIdleTimeoutPrefChanged();
  OnManagedScreensaverPhotoRefreshIntervalPrefChanged();
}

void AmbientController::RemoveAmbientModeSettingsPrefObservers() {
  for (const auto* pref_name :
       {ambient::prefs::kAmbientModeLockScreenBackgroundTimeoutSeconds,
        ambient::prefs::kAmbientModeLockScreenInactivityTimeoutSeconds,
        ambient::prefs::kAmbientModePhotoRefreshIntervalSeconds,
        ambient::prefs::kAmbientUiSettings,
        ambient::prefs::kAmbientModeAnimationPlaybackSpeed,
        ambient::prefs::kAmbientModeManagedScreensaverIdleTimeoutSeconds,
        ambient::prefs::
            kAmbientModeManagedScreensaverImageDisplayIntervalSeconds}) {
    if (pref_change_registrar_ &&
        pref_change_registrar_->IsObserved(pref_name)) {
      pref_change_registrar_->Remove(pref_name);
    }
    if (sign_in_pref_change_registrar_ &&
        sign_in_pref_change_registrar_->IsObserved(pref_name)) {
      sign_in_pref_change_registrar_->Remove(pref_name);
    }
  }
}

void AmbientController::OnManagedScreensaverLockScreenIdleTimeoutPrefChanged() {
  PrefService* pref_service = GetActivePrefService();
  CHECK(pref_service);
  ambient_ui_model_.SetLockScreenInactivityTimeout(
      base::Seconds(pref_service->GetInteger(
          ambient::prefs::kAmbientModeManagedScreensaverIdleTimeoutSeconds)));
}

void AmbientController::OnManagedScreensaverPhotoRefreshIntervalPrefChanged() {
  PrefService* pref_service = GetActivePrefService();
  CHECK(pref_service);
  ambient_ui_model_.SetPhotoRefreshInterval(
      base::Seconds(pref_service->GetInteger(
          ambient::prefs::
              kAmbientModeManagedScreensaverImageDisplayIntervalSeconds)));
}

void AmbientController::AddConsumerPrefObservers() {
  // Note: in case we ever want to enable the consumer screensaver on the
  // login screen we should change the pref_change_registrar here with
  // `GetActivePrefChangeRegistrar()` and the corresponding
  // `GetPrimaryUserPrefService()` with `GetActivePrefService()` in the actual
  // method calls.
  if (!pref_change_registrar_) {
    return;
  }

  pref_change_registrar_->Add(
      ambient::prefs::kAmbientModeLockScreenInactivityTimeoutSeconds,
      base::BindRepeating(
          &AmbientController::OnLockScreenInactivityTimeoutPrefChanged,
          weak_ptr_factory_.GetWeakPtr()));

  pref_change_registrar_->Add(
      ambient::prefs::kAmbientModeLockScreenBackgroundTimeoutSeconds,
      base::BindRepeating(
          &AmbientController::OnLockScreenBackgroundTimeoutPrefChanged,
          weak_ptr_factory_.GetWeakPtr()));

  pref_change_registrar_->Add(
      ambient::prefs::kAmbientModePhotoRefreshIntervalSeconds,
      base::BindRepeating(&AmbientController::OnPhotoRefreshIntervalPrefChanged,
                          weak_ptr_factory_.GetWeakPtr()));

  pref_change_registrar_->Add(
      ambient::prefs::kAmbientUiSettings,
      base::BindRepeating(&AmbientController::OnAmbientUiSettingsChanged,
                          weak_ptr_factory_.GetWeakPtr()));

  pref_change_registrar_->Add(
      ambient::prefs::kAmbientModeAnimationPlaybackSpeed,
      base::BindRepeating(&AmbientController::OnAnimationPlaybackSpeedChanged,
                          weak_ptr_factory_.GetWeakPtr()));

  // Trigger the callbacks manually the first time to init AmbientUiModel.
  OnLockScreenInactivityTimeoutPrefChanged();
  OnLockScreenBackgroundTimeoutPrefChanged();
  OnPhotoRefreshIntervalPrefChanged();
  OnAnimationPlaybackSpeedChanged();
}

void AmbientController::OnEnabledPrefChanged() {
  RecordManagedScreensaverEnabledPref();

  if (!IsAmbientModeEnabled()) {
    DVLOG(1) << "Ambient mode disabled";
    ResetAmbientControllerResources();
    return;
  }

  DVLOG(1) << "Ambient mode enabled";

  // A second initialization can happen in the following cases:
  // 1) Ambient mode is enabled for the login screen via device policy on a
  // managed device (first initialization), A consumer user with an email with
  // @gmail.com logins into the device and has ambient mode enabled (Second
  // initialization).
  //
  // 2) Ambient mode is enabled for the login screen via device policy on a
  // managed device (first initialization), A managed user logins into the
  // device and the managed screensaver is enabled via user policy. (Second
  // initialization).

  if (is_initialized_) {
    // In case the mode is initialized we reset and start from a clean slate so
    // that we do not double allocate everything and always listen to the
    // correct prefs.
    // Note: We do not early return here as multiple calls to this method are
    // valid and depending upon the type of ambient mode being enabled we have
    // to do different things.
    ResetAmbientControllerResources();
  }
  is_initialized_ = true;

  if (IsAmbientModeManagedScreensaverEnabled()) {
    AddManagedScreensaverPolicyPrefObservers();
  } else {
    AddConsumerPrefObservers();
  }

  CreateUiLauncher();

  ambient_ui_model_observer_.Observe(&ambient_ui_model_);
  auto* power_manager_client = chromeos::PowerManagerClient::Get();
  DCHECK(power_manager_client);
  power_manager_client_observer_.Observe(power_manager_client);

  fingerprint_->AddFingerprintObserver(
      fingerprint_observer_receiver_.BindNewPipeAndPassRemote());

  // The policy update can happen on the login screen as well so we need to
  // trigger the state change to start the ambient mode if required.
  if (IsAmbientModeManagedScreensaverEnabled()) {
    OnLoginLockStateChanged(GetLockScreenState());
  }
}

void AmbientController::ResetAmbientControllerResources() {
  SetUiVisibilityClosed();

  RemoveAmbientModeSettingsPrefObservers();

  ambient_ui_model_observer_.Reset();
  power_manager_client_observer_.Reset();

  DestroyUiLauncher();

  if (fingerprint_observer_receiver_.is_bound()) {
    fingerprint_observer_receiver_.reset();
  }
  is_initialized_ = false;
}

void AmbientController::OnLockScreenInactivityTimeoutPrefChanged() {
  auto* pref_service = GetPrimaryUserPrefService();
  if (!pref_service)
    return;

  ambient_ui_model_.SetLockScreenInactivityTimeout(
      base::Seconds(pref_service->GetInteger(
          ambient::prefs::kAmbientModeLockScreenInactivityTimeoutSeconds)));
}

void AmbientController::OnLockScreenBackgroundTimeoutPrefChanged() {
  auto* pref_service = GetPrimaryUserPrefService();
  if (!pref_service)
    return;

  ambient_ui_model_.SetBackgroundLockScreenTimeout(
      base::Seconds(pref_service->GetInteger(
          ambient::prefs::kAmbientModeLockScreenBackgroundTimeoutSeconds)));
}

void AmbientController::OnPhotoRefreshIntervalPrefChanged() {
  auto* pref_service = GetPrimaryUserPrefService();
  if (!pref_service)
    return;

  ambient_ui_model_.SetPhotoRefreshInterval(
      base::Seconds(pref_service->GetInteger(
          ambient::prefs::kAmbientModePhotoRefreshIntervalSeconds)));
}

void AmbientController::OnAmbientUiSettingsChanged() {
  DVLOG(4) << "AmbientUiSettings changed to "
           << GetCurrentUiSettings().ToString();
  // For a given topic category, the topics downloaded from IMAX and saved to
  // cache differ from theme to theme:
  // 1) Slideshow mode keeps primary/related photos paired within a topic,
  //    whereas animated themes split the photos into 2 separate topics.
  // 2) The resolution of the photos downloaded from FIFE may differ between
  //    themes, depending on the image assets' sizes in the animation file.
  // For this reason, it is better to not re-use the cache when switching
  // between themes.
  //
  // There are corner cases here where the theme may change and the program
  // crashes before the cache gets cleared below. This is intentionally not
  // accounted for because it's not worth the added complexity. If this
  // should happen, re-using the cache will still work without fatal behavior.
  // The UI may just not be optimal. Furthermore, the cache gradually gets
  // overwritten with topics reflecting the new theme anyways, so ambient mode
  // should not be stuck with a mismatched cache indefinitely.
  ambient_photo_cache::Clear(ambient_photo_cache::Store::kPrimary);

  // The |AmbientUiLauncher| implementation to use is largely dependent on
  // the current |AmbientUiSettings|, so this needs to be recreated.
  CreateUiLauncher();
}

void AmbientController::OnAnimationPlaybackSpeedChanged() {
  DCHECK(GetPrimaryUserPrefService());
  ambient_ui_model_.set_animation_playback_speed(
      GetPrimaryUserPrefService()->GetDouble(
          ambient::prefs::kAmbientModeAnimationPlaybackSpeed));
}

void AmbientController::RequestAccessToken(
    AmbientAccessTokenController::AccessTokenCallback callback,
    bool may_refresh_token_on_lock) {
  // Do not request access tokens when the ambient mode is in the managed mode
  // as we do not want to rely on any user information .
  if (IsAmbientModeManagedScreensaverEnabled()) {
    // Consume the callback to be resilient against dependencies on the callback
    // in the future.
    std::move(callback).Run("", "");
    return;
  }
  access_token_controller_.RequestAccessToken(std::move(callback),
                                              may_refresh_token_on_lock);
}

void AmbientController::DismissUI() {
  // Call `ClearPreTargetHandler` immediately so that `OnKeyEvent` has no
  // chance of being called and consuming the keypress.
  ClearPreTargetHandler();

  if (!IsAmbientModeEnabled()) {
    SetUiVisibilityClosed();
    return;
  }

  if (ambient_ui_model_.ui_visibility() == AmbientUiVisibility::kHidden) {
    // Double resetting crashes the UI, make sure it is running.
    if (inactivity_timer_.IsRunning()) {
      inactivity_timer_.Reset();
    }
    return;
  }

  if (LockScreen::HasInstance()) {
    SetUiVisibilityHidden();
    return;
  }

  SetUiVisibilityClosed();
}

AmbientBackendModel* AmbientController::GetAmbientBackendModel() {
  // This can legitimately be null. Some ambient UIs do not use photos at all
  // and hence, do not have an active |AmbientBackendModel|.
  // TODO(b/274164306): Move |AmbientBackendModel| references completely out
  // of |AmbientController|. The business logic should be migrated elsewhere
  // (likely somewhere within an |AmbientUiLauncher| implementation).
  return ambient_ui_launcher_->GetAmbientBackendModel();
}

AmbientWeatherModel* AmbientController::GetAmbientWeatherModel() {
  return ambient_weather_controller_->weather_model();
}

std::unique_ptr<views::Widget> AmbientController::CreateWidget(
    aura::Window* container) {
  if (ui_launcher_state_ != AmbientUiLauncherState::kRendering) {
    return nullptr;
  }

  CHECK(session_metrics_recorder_);
  session_metrics_recorder_->RegisterScreen();
  std::unique_ptr<AmbientContainerView> container_view;
  container_view = std::make_unique<AmbientContainerView>(
      GetCurrentUiSettings(), ambient_ui_launcher_->CreateView());
  auto* widget_delegate = new AmbientWidgetDelegate();
  widget_delegate->SetInitiallyFocusedView(container_view.get());

  views::Widget::InitParams params(
      views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET,
      views::Widget::InitParams::TYPE_WINDOW_FRAMELESS);
  params.name = GetWidgetName();
  params.show_state = ui::SHOW_STATE_FULLSCREEN;
  params.parent = container;
  params.delegate = widget_delegate;
  params.visible_on_all_workspaces = true;

  // Do not change the video wake lock.
  params.opacity = views::Widget::InitParams::WindowOpacity::kTranslucent;

  auto widget = std::make_unique<views::Widget>();
  widget->Init(std::move(params));
  auto* contents_view = widget->SetContentsView(std::move(container_view));

  widget->SetVisibilityAnimationTransition(
      views::Widget::VisibilityTransition::ANIMATE_BOTH);
  ::wm::SetWindowVisibilityAnimationType(
      widget->GetNativeWindow(), ::wm::WINDOW_VISIBILITY_ANIMATION_TYPE_FADE);
  ::wm::SetWindowVisibilityChangesAnimated(widget->GetNativeWindow());

  widget->Show();

  // Only announce for the primary window.
  if (Shell::GetPrimaryRootWindow() == container->GetRootWindow()) {
    contents_view->GetViewAccessibility().AnnounceText(
        l10n_util::GetStringUTF16(IDS_ASH_SCREENSAVER_STARTS));
  }

  return widget;
}

void AmbientController::OnUiLauncherInitialized(bool success) {
  CHECK(session_metrics_recorder_);
  session_metrics_recorder_->SetInitStatus(success);
  if (!success) {
    // Success = false denotes a case where the screensaver is in a permanent
    // error state and such that the UI and any further attempts to launch the
    // UI will also result in this failure.
    // TODO (b/175142676) Add metrics for cases where success = false.
    LOG(ERROR) << "AmbientUiLauncher failed to initialize";
    SetUiVisibilityClosed();
    return;
  }
  ui_launcher_state_ = AmbientUiLauncherState::kRendering;
  CreateAndShowWidgets();
}

void AmbientController::CreateAndShowWidgets() {
  if (ambient_ui_model_.ui_visibility() == AmbientUiVisibility::kPreview) {
    preview_widget_created_at_ = base::Time::Now();
  }
  // Hide cursor.
  Shell::Get()->cursor_manager()->HideCursor();
  for (auto* root_window_controller :
       RootWindowController::root_window_controllers()) {
    root_window_controller->CreateAmbientWidget();
  }
}

void AmbientController::StopScreensaver() {
  CloseAllWidgets(close_widgets_immediately_);
  session_metrics_recorder_.reset();
  ui_launcher_init_callback_.Cancel();
  ui_launcher_state_ = AmbientUiLauncherState::kInactive;
  ambient_ui_launcher_->Finalize();
}

void AmbientController::MaybeStartScreenSaver() {
  // The screensaver may have already been started.
  if (IsUiLauncherActive()) {
    return;
  }

  if (!user_activity_observer_.IsObserving())
    user_activity_observer_.Observe(ui::UserActivityDetector::Get());

  // Add observer for assistant interaction model
  AssistantInteractionController::Get()->GetModel()->AddObserver(this);

  session_metrics_recorder_ = std::make_unique<AmbientSessionMetricsRecorder>(
      ambient_ui_launcher_->CreateMetricsDelegate(GetCurrentUiSettings()));

  SetUpPreTargetHandler();

  ui_launcher_init_callback_.Reset(
      base::BindOnce(&AmbientController::OnUiLauncherInitialized,
                     weak_ptr_factory_.GetWeakPtr()));
  ui_launcher_state_ = AmbientUiLauncherState::kInitializing;
  ambient_ui_launcher_->Initialize(ui_launcher_init_callback_.callback());
}

AmbientUiSettings AmbientController::GetCurrentUiSettings() const {
  CHECK(GetActivePrefService());
  return AmbientUiSettings::ReadFromPrefService(*GetActivePrefService());
}

void AmbientController::MaybeDismissUIOnMouseMove() {
  // If the move was not an actual mouse move event or the screen saver widget
  // is not shown yet (images are not downloaded), don't dismiss.
  if (!last_mouse_event_was_move_ || !IsShowing()) {
    return;
  }

  // In preview mode, don't dismiss until the timer stops running (avoids
  // accidental dismissal).
  if (ambient_ui_model_.ui_visibility() == AmbientUiVisibility::kPreview) {
    auto elapsed = base::Time::Now() - preview_widget_created_at_;
    if (elapsed < kDismissPreviewOnMouseMoveDelay) {
      return;
    }
  }
  DismissUI();
}

void AmbientController::CreateUiLauncher() {
  if (IsUiLauncherActive()) {
    // There are no known use cases where the AmbientUiSettings selected by the
    // user can change while in the middle of an ambient session, but this is
    // handled gracefully just in case.
    LOG(DFATAL) << "Cannot reset the AmbientUiLauncher while it is active";
    return;
  }

  DestroyUiLauncher();

  if (IsAmbientModeManagedScreensaverEnabled()) {
    ambient_ui_launcher_ = std::make_unique<AmbientManagedSlideshowUiLauncher>(
        &delegate_, screensaver_images_policy_handler_.get());
  } else {
    switch (GetCurrentUiSettings().theme()) {
      case personalization_app::mojom::AmbientTheme::kSlideshow:
        ambient_ui_launcher_ =
            std::make_unique<AmbientSlideshowUiLauncher>(&delegate_);
        break;
      case personalization_app::mojom::AmbientTheme::kFeelTheBreeze:
      case personalization_app::mojom::AmbientTheme::kFloatOnBy:
        ambient_ui_launcher_ = std::make_unique<AmbientAnimationUiLauncher>(
            GetCurrentUiSettings(), &delegate_);
        break;
      case personalization_app::mojom::AmbientTheme::kVideo:
        ambient_ui_launcher_ = std::make_unique<AmbientVideoUiLauncher>(
            GetPrimaryUserPrefService(), &delegate_);
        break;
    }
  }

  ambient_ui_launcher_->SetObserver(this);
}

void AmbientController::DestroyUiLauncher() {
  ui_launcher_state_ = AmbientUiLauncherState::kInactive;
  ambient_ui_launcher_.reset();
}

bool AmbientController::IsUiLauncherActive() const {
  return ui_launcher_state_ != AmbientUiLauncherState::kInactive;
}

void AmbientController::OnReadyStateChanged(bool is_ready) {
  if (!is_ready) {
    // Close the UI if the launcher isn't ready. This is done so that we can
    // stop the current ui launcher session and prevent screenburn.
    SetUiVisibilityClosed();
    return;
  }
  // In case the ready state changes on the login/lock screen we should re-show
  // the ambient mode.
  OnLoginLockStateChanged(GetLockScreenState());
}

void AmbientController::MaybeStopUiEventPropagation(ui::Event* event) {
  // If ambient resources are still be loading and the UI has not started
  // rendering yet (which is usually just a few seconds), UI events such as
  // key presses should still be propagated to the current UI (ex: the lock
  // screen).
  if (IsShowing()) {
    event->StopPropagation();
  }
}

}  // namespace ash