blob: 868d0bbd7931276565791e08fd83923810fdc776 [file] [log] [blame]
// 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/ambient/ambient_controller.h"
#include <memory>
#include <string>
#include <utility>
#include <vector>
#include "ash/ambient/model/ambient_backend_model_observer.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/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_metrics.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_ui_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/system/power/power_status.h"
#include "base/bind.h"
#include "base/callback_helpers.h"
#include "base/check.h"
#include "base/memory/weak_ptr.h"
#include "base/time/time.h"
#include "base/timer/timer.h"
#include "build/buildflag.h"
#include "chromeos/assistant/buildflags.h"
#include "chromeos/constants/chromeos_features.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 "chromeos/services/assistant/public/cpp/assistant_service.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/ui_base_types.h"
#include "ui/base/user_activity/user_activity_detector.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";
void CloseAssistantUi() {
DCHECK(AssistantUiController::Get());
AssistantUiController::Get()->CloseUi(
chromeos::assistant::AssistantExitPoint::kUnspecified);
}
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 full or battery is charging, that implies power is
// connected. Also return true if a power source is connected and
// battery is not discharging.
return power_status->IsBatteryCharging() || power_status->IsBatteryFull() ||
(power_status->IsLinePowerConnected() &&
!power_status->IsBatteryDischargingOnLinePower());
} 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();
}
bool IsAmbientModeEnabled() {
if (!AmbientClient::Get()->IsAmbientModeAllowed())
return false;
auto* pref_service = GetPrimaryUserPrefService();
return pref_service &&
pref_service->GetBoolean(ambient::prefs::kAmbientModeEnabled);
}
class AmbientWidgetDelegate : public views::WidgetDelegate {
public:
AmbientWidgetDelegate() { SetCanMaximize(true); }
};
} // namespace
// static
void AmbientController::RegisterProfilePrefs(PrefRegistrySimple* registry) {
if (chromeos::features::IsAmbientModeEnabled()) {
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());
}
}
AmbientController::AmbientController(
mojo::PendingRemote<device::mojom::Fingerprint> fingerprint)
: fingerprint_(std::move(fingerprint)) {
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());
}
AmbientController::~AmbientController() {
CloseAllWidgets(/*immediately=*/true);
}
void AmbientController::OnAmbientUiVisibilityChanged(
AmbientUiVisibility visibility) {
switch (visibility) {
case AmbientUiVisibility::kShown:
// Record metrics on ambient mode usage.
ambient::RecordAmbientModeActivation(
/*ui_mode=*/LockScreen::HasInstance() ? AmbientUiMode::kLockScreenUi
: AmbientUiMode::kInSessionUi,
/*tablet_mode=*/Shell::Get()->IsInTabletMode());
DCHECK(!start_time_);
start_time_ = base::Time::Now();
// Cancels the timer upon shown.
inactivity_timer_.Stop();
if (IsChargerConnected()) {
// Requires wake lock to prevent display from sleeping.
AcquireWakeLock();
}
// 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());
if (!user_activity_observer_.IsObserving())
user_activity_observer_.Observe(ui::UserActivityDetector::Get());
StartRefreshingImages();
break;
case AmbientUiVisibility::kHidden:
case AmbientUiVisibility::kClosed:
CloseAllWidgets(/*immediately=*/false);
// 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.
StopRefreshingImages();
// We close the Assistant UI after ambient screen not being shown to sync
// states to |AssistantUiController|. This will be a no-op if the
// |kAmbientAssistant| feature is disabled, or the Assistant UI has
// already been closed.
CloseAssistantUi();
// Should do nothing if the wake lock has already been released.
ReleaseWakeLock();
// |start_time_| may be empty in case of |AmbientUiVisibility::kHidden| if
// ambient mode has just started.
if (start_time_) {
auto elapsed = base::Time::Now() - start_time_.value();
DVLOG(2) << "Exit ambient mode. Elapsed time: " << elapsed;
ambient::RecordAmbientModeTimeElapsed(
/*time_delta=*/elapsed,
/*tablet_mode=*/Shell::Get()->IsInTabletMode());
start_time_.reset();
}
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.
ambient_ui_model_.SetUiVisibility(AmbientUiVisibility::kShown);
}
void AmbientController::OnLockStateChanged(bool locked) {
if (!locked) {
// Ambient screen will be destroyed along with the lock screen when user
// logs in.
CloseUi();
return;
}
if (!IsAmbientModeEnabled()) {
VLOG(1) << "Ambient mode is not allowed.";
return;
}
// Reset image failures to allow retrying ambient mode after lock state
// changes.
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 (!IsShown()) {
// When lock screen starts, we don't immediately show the UI. The Ui is
// hidden and will show after a delay.
ShowHiddenUi();
}
}
void AmbientController::OnFirstSessionStarted() {
if (IsAmbientModeEnabled())
ambient_photo_controller_.ScheduleFetchBackupImages();
}
void AmbientController::OnActiveUserPrefServiceChanged(
PrefService* pref_service) {
if (!AmbientClient::Get()->IsAmbientModeAllowed() ||
GetPrimaryUserPrefService() != pref_service) {
return;
}
pref_change_registrar_ = std::make_unique<PrefChangeRegistrar>();
pref_change_registrar_->Init(pref_service);
pref_change_registrar_->Add(
ambient::prefs::kAmbientModeEnabled,
base::BindRepeating(&AmbientController::OnEnabledPrefChanged,
weak_ptr_factory_.GetWeakPtr()));
OnEnabledPrefChanged();
}
void AmbientController::OnPowerStatusChanged() {
if (ambient_ui_model_.ui_visibility() != AmbientUiVisibility::kShown) {
// No action needed if ambient screen is not shown.
return;
}
if (IsChargerConnected()) {
AcquireWakeLock();
} else {
ReleaseWakeLock();
}
}
void AmbientController::ScreenIdleStateChanged(
const power_manager::ScreenIdleState& idle_state) {
DVLOG(1) << "ScreenIdleStateChanged: dimmed(" << idle_state.dimmed()
<< ") off(" << idle_state.off() << ")";
if (!IsAmbientModeEnabled())
return;
if (idle_state.off()) {
DVLOG(1) << "Screen is off, close ambient mode.";
CloseAllWidgets(/*immediately=*/true);
CloseUi();
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()->ImageLoadingFailed()) {
VLOG(1) << "Skipping ambient mode activation due to prior failure";
return;
}
ShowUi();
return;
}
if (LockScreen::HasInstance() &&
ambient_ui_model_.ui_visibility() == AmbientUiVisibility::kClosed) {
// Restart hidden ui if the screen is back on and lockscreen is shown.
ShowHiddenUi();
}
}
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.
CloseAllWidgets(/*immediately=*/true);
CloseUi();
}
void AmbientController::OnAuthScanDone(
device::mojom::ScanResult scan_result,
const base::flat_map<std::string, std::vector<std::string>>& matches) {
DismissUI();
}
void AmbientController::OnUserActivity(const ui::Event* event) {
DismissUI();
}
void AmbientController::AddAmbientViewDelegateObserver(
AmbientViewDelegateObserver* observer) {
delegate_.AddObserver(observer);
}
void AmbientController::RemoveAmbientViewDelegateObserver(
AmbientViewDelegateObserver* observer) {
delegate_.RemoveObserver(observer);
}
void AmbientController::ShowUi() {
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;
}
ambient_ui_model_.SetUiVisibility(AmbientUiVisibility::kShown);
}
void AmbientController::ShowHiddenUi() {
DVLOG(1) << __func__;
if (!IsAmbientModeEnabled()) {
LOG(WARNING) << "Ambient mode is not allowed.";
return;
}
ambient_ui_model_.SetUiVisibility(AmbientUiVisibility::kHidden);
}
void AmbientController::CloseUi() {
DVLOG(1) << __func__;
ambient_ui_model_.SetUiVisibility(AmbientUiVisibility::kClosed);
GetAmbientBackendModel()->ResetImageFailures();
}
void AmbientController::ToggleInSessionUi() {
if (ambient_ui_model_.ui_visibility() == AmbientUiVisibility::kClosed)
ShowUi();
else
CloseUi();
}
bool AmbientController::IsShown() const {
return ambient_ui_model_.ui_visibility() == AmbientUiVisibility::kShown;
}
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();
}
void AmbientController::CloseAllWidgets(bool immediately) {
for (auto* root_window_controller :
RootWindowController::root_window_controllers()) {
root_window_controller->CloseAmbientWidget(immediately);
}
}
void AmbientController::OnEnabledPrefChanged() {
// TODO(b/176094707) conditionally create/destroy photo_controller and cache
// if Ambient is enabled
ambient_photo_controller_.InitCache();
if (IsAmbientModeEnabled()) {
DVLOG(1) << "Ambient mode enabled";
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()));
// Trigger the callbacks manually the first time to init AmbientUiModel.
OnLockScreenInactivityTimeoutPrefChanged();
OnLockScreenBackgroundTimeoutPrefChanged();
OnPhotoRefreshIntervalPrefChanged();
ambient_ui_model_observer_.Observe(&ambient_ui_model_);
ambient_backend_model_observer_.Observe(
ambient_photo_controller_.ambient_backend_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());
} else {
DVLOG(1) << "Ambient mode disabled";
CloseUi();
for (const auto* pref_name :
{ambient::prefs::kAmbientModeLockScreenBackgroundTimeoutSeconds,
ambient::prefs::kAmbientModeLockScreenInactivityTimeoutSeconds,
ambient::prefs::kAmbientModePhotoRefreshIntervalSeconds}) {
if (pref_change_registrar_->IsObserved(pref_name))
pref_change_registrar_->Remove(pref_name);
}
ambient_ui_model_observer_.Reset();
ambient_backend_model_observer_.Reset();
power_manager_client_observer_.Reset();
if (fingerprint_observer_receiver_.is_bound())
fingerprint_observer_receiver_.reset();
}
}
void AmbientController::OnLockScreenInactivityTimeoutPrefChanged() {
auto* pref_service = GetPrimaryUserPrefService();
if (!pref_service)
return;
ambient_ui_model_.SetLockScreenInactivityTimeout(
base::TimeDelta::FromSeconds(pref_service->GetInteger(
ambient::prefs::kAmbientModeLockScreenInactivityTimeoutSeconds)));
}
void AmbientController::OnLockScreenBackgroundTimeoutPrefChanged() {
auto* pref_service = GetPrimaryUserPrefService();
if (!pref_service)
return;
ambient_ui_model_.SetBackgroundLockScreenTimeout(
base::TimeDelta::FromSeconds(pref_service->GetInteger(
ambient::prefs::kAmbientModeLockScreenBackgroundTimeoutSeconds)));
}
void AmbientController::OnPhotoRefreshIntervalPrefChanged() {
auto* pref_service = GetPrimaryUserPrefService();
if (!pref_service)
return;
ambient_ui_model_.SetPhotoRefreshInterval(
base::TimeDelta::FromSeconds(pref_service->GetInteger(
ambient::prefs::kAmbientModePhotoRefreshIntervalSeconds)));
}
void AmbientController::RequestAccessToken(
AmbientAccessTokenController::AccessTokenCallback callback,
bool may_refresh_token_on_lock) {
access_token_controller_.RequestAccessToken(std::move(callback),
may_refresh_token_on_lock);
}
void AmbientController::DismissUI() {
if (!IsAmbientModeEnabled()) {
CloseUi();
return;
}
if (ambient_ui_model_.ui_visibility() == AmbientUiVisibility::kHidden) {
inactivity_timer_.Reset();
return;
}
if (LockScreen::HasInstance()) {
ShowHiddenUi();
return;
}
CloseUi();
}
AmbientBackendModel* AmbientController::GetAmbientBackendModel() {
return ambient_photo_controller_.ambient_backend_model();
}
void AmbientController::OnImagesReady() {
CreateAndShowWidgets();
}
void AmbientController::OnImagesFailed() {
LOG(ERROR) << "Ambient mode failed to start";
CloseUi();
}
std::unique_ptr<views::Widget> AmbientController::CreateWidget(
aura::Window* container) {
auto container_view = std::make_unique<AmbientContainerView>(&delegate_);
auto* widget_delegate = new AmbientWidgetDelegate();
widget_delegate->SetInitiallyFocusedView(container_view.get());
views::Widget::InitParams params;
params.type = views::Widget::InitParams::TYPE_WINDOW_FRAMELESS;
params.name = GetWidgetName();
params.show_state = ui::SHOW_STATE_FULLSCREEN;
params.parent = container;
params.ownership = views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
params.delegate = widget_delegate;
params.visible_on_all_workspaces = true;
auto widget = std::make_unique<views::Widget>();
widget->Init(std::move(params));
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();
return widget;
}
void AmbientController::CreateAndShowWidgets() {
// Hide cursor.
Shell::Get()->cursor_manager()->HideCursor();
for (auto* root_window_controller :
RootWindowController::root_window_controllers()) {
root_window_controller->CreateAmbientWidget();
}
}
void AmbientController::StartRefreshingImages() {
ambient_photo_controller_.StartScreenUpdate();
}
void AmbientController::StopRefreshingImages() {
ambient_photo_controller_.StopScreenUpdate();
}
void AmbientController::set_backend_controller_for_testing(
std::unique_ptr<AmbientBackendController> backend_controller) {
ambient_backend_controller_ = std::move(backend_controller);
}
} // namespace ash