blob: fedb3326d6b4bda6d6a51cce046a835e890d5f13 [file] [log] [blame]
// Copyright 2020 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 <memory>
#include <string>
#include <utility>
#include "ash/ambient/ambient_constants.h"
#include "ash/ambient/ambient_managed_photo_controller.h"
#include "ash/ambient/ambient_ui_settings.h"
#include "ash/ambient/test/ambient_ash_test_base.h"
#include "ash/ambient/ui/ambient_container_view.h"
#include "ash/ambient/ui/ambient_view_ids.h"
#include "ash/assistant/assistant_interaction_controller_impl.h"
#include "ash/assistant/model/assistant_interaction_model.h"
#include "ash/constants/ambient_theme.h"
#include "ash/constants/ambient_video.h"
#include "ash/constants/ash_features.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/fake_ambient_backend_controller_impl.h"
#include "ash/public/cpp/assistant/controller/assistant_interaction_controller.h"
#include "ash/public/cpp/personalization_app/time_of_day_paths.h"
#include "ash/public/cpp/test/in_process_image_decoder.h"
#include "ash/root_window_controller.h"
#include "ash/shell.h"
#include "ash/system/power/power_status.h"
#include "ash/test/test_ash_web_view.h"
#include "ash/wm/tablet_mode/tablet_mode_controller.h"
#include "base/files/scoped_temp_dir.h"
#include "base/functional/callback.h"
#include "base/location.h"
#include "base/ranges/algorithm.h"
#include "base/run_loop.h"
#include "base/scoped_observation.h"
#include "base/strings/strcat.h"
#include "base/task/sequenced_task_runner.h"
#include "base/test/bind.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/metrics/user_action_tester.h"
#include "base/test/scoped_feature_list.h"
#include "base/test/scoped_run_loop_timeout.h"
#include "base/time/time.h"
#include "build/buildflag.h"
#include "chromeos/ash/components/assistant/buildflags.h"
#include "chromeos/ash/services/libassistant/public/cpp/assistant_interaction_metadata.h"
#include "chromeos/dbus/power/fake_power_manager_client.h"
#include "chromeos/dbus/power/power_manager_client.h"
#include "chromeos/dbus/power_manager/power_supply_properties.pb.h"
#include "chromeos/dbus/power_manager/suspend.pb.h"
#include "net/base/url_util.h"
#include "ui/events/event.h"
#include "ui/events/keycodes/keyboard_codes_posix.h"
#include "ui/events/platform/platform_event_source.h"
#include "ui/events/pointer_details.h"
#include "ui/events/types/event_type.h"
namespace ash {
namespace {
using assistant::AssistantInteractionMetadata;
constexpr char kUser1[] = "user1@gmail.com";
constexpr char kUser2[] = "user2@gmail.com";
class AmbientUiVisibilityBarrier : public AmbientUiModelObserver {
public:
explicit AmbientUiVisibilityBarrier(AmbientUiVisibility target_visibility)
: target_visibility_(target_visibility) {
observation_.Observe(AmbientUiModel::Get());
}
AmbientUiVisibilityBarrier(const AmbientUiVisibilityBarrier&) = delete;
AmbientUiVisibilityBarrier& operator=(const AmbientUiVisibilityBarrier&) =
delete;
~AmbientUiVisibilityBarrier() override = default;
void WaitWithTimeout(base::TimeDelta timeout) {
if (AmbientUiModel::Get()->ui_visibility() == target_visibility_)
return;
base::test::ScopedRunLoopTimeout run_loop_timeout(FROM_HERE, timeout);
base::RunLoop run_loop;
run_loop_quit_closure_ = run_loop.QuitClosure();
run_loop.Run();
}
private:
void OnAmbientUiVisibilityChanged(AmbientUiVisibility visibility) override {
if (visibility == target_visibility_ && run_loop_quit_closure_) {
// Post task so that any existing tasks get run before WaitWithTimeout()
// completes.
base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE, std::move(run_loop_quit_closure_));
}
}
const AmbientUiVisibility target_visibility_;
base::ScopedObservation<AmbientUiModel, AmbientUiModelObserver> observation_{
this};
base::RepeatingClosure run_loop_quit_closure_;
};
} // namespace
class AmbientControllerTest : public AmbientAshTestBase {
public:
AmbientControllerTest() = default;
~AmbientControllerTest() override = default;
// AmbientAshTestBase:
void SetUp() override {
feature_list_.InitAndEnableFeature(features::kAmbientModeThrottleAnimation);
AmbientAshTestBase::SetUp();
GetSessionControllerClient()->set_show_lock_screen_views(true);
}
bool IsPrefObserved(const std::string& pref_name) {
auto* pref_change_registrar =
ambient_controller()->pref_change_registrar_.get();
DCHECK(pref_change_registrar);
return pref_change_registrar->IsObserved(pref_name);
}
bool WidgetsVisible() {
const auto& views = GetContainerViews();
return !views.empty() && base::ranges::all_of(views, [](const auto* view) {
return view->GetWidget()->IsVisible();
});
}
bool AreSessionSpecificObserversBound() {
auto* ctrl = ambient_controller();
bool ui_model_bound = ctrl->ambient_ui_model_observer_.IsObserving();
bool backend_model_bound =
ctrl->ambient_backend_model_observer_.IsObserving();
bool power_manager_bound =
ctrl->power_manager_client_observer_.IsObserving();
bool fingerprint_bound = ctrl->fingerprint_observer_receiver_.is_bound();
EXPECT_EQ(ui_model_bound, backend_model_bound)
<< "observers should all have the same state";
EXPECT_EQ(ui_model_bound, power_manager_bound)
<< "observers should all have the same state";
EXPECT_EQ(ui_model_bound, fingerprint_bound)
<< "observers should all have the same state";
return ui_model_bound;
}
base::test::ScopedFeatureList feature_list_;
protected:
base::UserActionTester user_action_tester_;
};
// Tests for behavior that are agnostic to the AmbientUiSettings selected by
// the user should use this test harness.
//
// Currently there are test cases that actually fall under this category but
// do not use this test fixture. This is done purely for time constraint reasons
// (it takes a lot of compute time to repeat every single one of these test
// cases).
class AmbientControllerTestForAnyUiSettings
: public AmbientControllerTest,
public ::testing::WithParamInterface<AmbientUiSettings> {
protected:
void SetUp() override {
AmbientControllerTest::SetUp();
SetAmbientUiSettings(GetParam());
}
};
INSTANTIATE_TEST_SUITE_P(
AllUiSettings,
AmbientControllerTestForAnyUiSettings,
// Only one lottie-animated theme and video is
// sufficient here. The main goal here is to make sure
// that fundamental behavior holds for all themes.
testing::Values(AmbientUiSettings(AmbientTheme::kSlideshow),
AmbientUiSettings(AmbientTheme::kVideo,
AmbientVideo::kNewMexico)
#if BUILDFLAG(HAS_ASH_AMBIENT_ANIMATION_RESOURCES)
,
AmbientUiSettings(AmbientTheme::kFeelTheBreeze)
#endif // BUILDFLAG(HAS_ASH_AMBIENT_ANIMATION_RESOURCES)
));
TEST_P(AmbientControllerTestForAnyUiSettings, ShowAmbientScreenUponLock) {
LockScreen();
// Lockscreen will not immediately show Ambient mode.
EXPECT_FALSE(ambient_controller()->IsShown());
// Ambient mode will show after inacivity and successfully loading first
// image.
FastForwardToLockScreenTimeout();
FastForwardTiny();
EXPECT_FALSE(GetContainerViews().empty());
EXPECT_EQ(AmbientUiModel::Get()->ui_visibility(),
AmbientUiVisibility::kShown);
EXPECT_TRUE(ambient_controller()->IsShown());
// Clean up.
UnlockScreen();
EXPECT_FALSE(ambient_controller()->IsShown());
}
TEST_P(AmbientControllerTestForAnyUiSettings,
NotShowAmbientWhenPrefNotEnabled) {
SetAmbientModeEnabled(false);
LockScreen();
// Lockscreen will not immediately show Ambient mode.
EXPECT_FALSE(ambient_controller()->IsShown());
// Ambient mode will not show after inacivity and successfully loading first
// image.
FastForwardToLockScreenTimeout();
FastForwardTiny();
EXPECT_TRUE(GetContainerViews().empty());
EXPECT_EQ(AmbientUiModel::Get()->ui_visibility(),
AmbientUiVisibility::kClosed);
EXPECT_FALSE(ambient_controller()->IsShown());
// Clean up.
UnlockScreen();
EXPECT_FALSE(ambient_controller()->IsShown());
}
TEST_P(AmbientControllerTestForAnyUiSettings, HideAmbientScreen) {
LockScreen();
FastForwardToLockScreenTimeout();
FastForwardTiny();
EXPECT_FALSE(GetContainerViews().empty());
EXPECT_EQ(AmbientUiModel::Get()->ui_visibility(),
AmbientUiVisibility::kShown);
EXPECT_TRUE(ambient_controller()->IsShown());
HideAmbientScreen();
FastForwardTiny();
EXPECT_TRUE(GetContainerViews().empty());
EXPECT_EQ(AmbientUiModel::Get()->ui_visibility(),
AmbientUiVisibility::kHidden);
// Clean up.
UnlockScreen();
EXPECT_FALSE(ambient_controller()->IsShown());
}
TEST_P(AmbientControllerTestForAnyUiSettings, CloseAmbientScreenUponUnlock) {
LockScreen();
FastForwardToLockScreenTimeout();
FastForwardTiny();
EXPECT_FALSE(GetContainerViews().empty());
EXPECT_EQ(AmbientUiModel::Get()->ui_visibility(),
AmbientUiVisibility::kShown);
EXPECT_TRUE(ambient_controller()->IsShown());
UnlockScreen();
EXPECT_EQ(AmbientUiModel::Get()->ui_visibility(),
AmbientUiVisibility::kClosed);
EXPECT_FALSE(ambient_controller()->IsShown());
// The view should be destroyed along the widget.
FastForwardTiny();
EXPECT_TRUE(GetContainerViews().empty());
}
TEST_P(AmbientControllerTestForAnyUiSettings,
CloseAmbientScreenUponUnlockSecondaryUser) {
// Simulate the login screen.
ClearLogin();
SimulateUserLogin(kUser1);
SetAmbientModeEnabled(true);
LockScreen();
FastForwardToLockScreenTimeout();
FastForwardTiny();
EXPECT_FALSE(GetContainerViews().empty());
EXPECT_EQ(AmbientUiModel::Get()->ui_visibility(),
AmbientUiVisibility::kShown);
EXPECT_TRUE(ambient_controller()->IsShown());
SimulateUserLogin(kUser2);
EXPECT_EQ(AmbientUiModel::Get()->ui_visibility(),
AmbientUiVisibility::kClosed);
EXPECT_FALSE(ambient_controller()->IsShown());
// The view should be destroyed along the widget.
FastForwardTiny();
EXPECT_TRUE(GetContainerViews().empty());
FastForwardToLockScreenTimeout();
FastForwardTiny();
EXPECT_EQ(AmbientUiModel::Get()->ui_visibility(),
AmbientUiVisibility::kClosed);
EXPECT_FALSE(ambient_controller()->IsShown());
// The view should be destroyed along the widget.
FastForwardTiny();
EXPECT_TRUE(GetContainerViews().empty());
}
TEST_F(AmbientControllerTest,
CloseAmbientScreenUponPowerButtonClickInTabletMode) {
Shell::Get()->tablet_mode_controller()->SetEnabledForTest(true);
LockScreen();
FastForwardToLockScreenTimeout();
FastForwardTiny();
EXPECT_FALSE(GetContainerViews().empty());
EXPECT_TRUE(ambient_controller()->IsShown());
SimulatePowerButtonClick();
EXPECT_EQ(AmbientUiModel::Get()->ui_visibility(),
AmbientUiVisibility::kClosed);
EXPECT_FALSE(ambient_controller()->IsShown());
// The view should be destroyed along the widget.
EXPECT_TRUE(GetContainerViews().empty());
}
TEST_F(AmbientControllerTest, NotShowAmbientWhenLockSecondaryUser) {
// Simulate the login screen.
ClearLogin();
SimulateUserLogin(kUser1);
SetAmbientModeEnabled(true);
LockScreen();
FastForwardToLockScreenTimeout();
FastForwardTiny();
EXPECT_FALSE(GetContainerViews().empty());
EXPECT_EQ(AmbientUiModel::Get()->ui_visibility(),
AmbientUiVisibility::kShown);
EXPECT_TRUE(ambient_controller()->IsShown());
SimulateUserLogin(kUser2);
SetAmbientModeEnabled(true);
// Ambient mode should not show for second user even if that user has the pref
// turned on.
EXPECT_EQ(AmbientUiModel::Get()->ui_visibility(),
AmbientUiVisibility::kClosed);
EXPECT_FALSE(ambient_controller()->IsShown());
// The view should be destroyed along the widget.
FastForwardTiny();
EXPECT_TRUE(GetContainerViews().empty());
LockScreen();
FastForwardToLockScreenTimeout();
FastForwardTiny();
EXPECT_EQ(AmbientUiModel::Get()->ui_visibility(),
AmbientUiVisibility::kClosed);
EXPECT_FALSE(ambient_controller()->IsShown());
// The view should be destroyed along the widget.
EXPECT_TRUE(GetContainerViews().empty());
}
TEST_P(AmbientControllerTestForAnyUiSettings,
ShouldRequestAccessTokenWhenLockingScreen) {
EXPECT_FALSE(IsAccessTokenRequestPending());
// Lock the screen will request a token.
LockScreen();
EXPECT_TRUE(IsAccessTokenRequestPending());
IssueAccessToken(/*is_empty=*/false);
EXPECT_FALSE(IsAccessTokenRequestPending());
// Should close ambient widget already when unlocking screen.
UnlockScreen();
EXPECT_FALSE(IsAccessTokenRequestPending());
}
TEST_F(AmbientControllerTest, ShouldNotRequestAccessTokenWhenPrefNotEnabled) {
SetAmbientModeEnabled(false);
EXPECT_FALSE(IsAccessTokenRequestPending());
// Lock the screen will not request a token.
LockScreen();
EXPECT_FALSE(IsAccessTokenRequestPending());
UnlockScreen();
EXPECT_FALSE(IsAccessTokenRequestPending());
}
TEST_P(AmbientControllerTestForAnyUiSettings, ShouldReturnCachedAccessToken) {
EXPECT_FALSE(IsAccessTokenRequestPending());
// Lock the screen will request a token.
LockScreen();
EXPECT_TRUE(IsAccessTokenRequestPending());
IssueAccessToken(/*is_empty=*/false);
EXPECT_FALSE(IsAccessTokenRequestPending());
// Another token request will return cached token.
base::OnceClosure closure = base::MakeExpectedRunClosure(FROM_HERE);
base::RunLoop run_loop;
ambient_controller()->RequestAccessToken(base::BindLambdaForTesting(
[&](const std::string& gaia_id, const std::string& access_token_fetched) {
EXPECT_EQ(access_token_fetched, TestAmbientClient::kTestAccessToken);
std::move(closure).Run();
run_loop.Quit();
}));
EXPECT_FALSE(IsAccessTokenRequestPending());
run_loop.Run();
// Clean up.
CloseAmbientScreen();
}
TEST_F(AmbientControllerTest, ShouldReturnEmptyAccessToken) {
EXPECT_FALSE(IsAccessTokenRequestPending());
// Lock the screen will request a token.
LockScreen();
EXPECT_TRUE(IsAccessTokenRequestPending());
IssueAccessToken(/*is_empty=*/false);
EXPECT_FALSE(IsAccessTokenRequestPending());
// Another token request will return cached token.
base::OnceClosure closure = base::MakeExpectedRunClosure(FROM_HERE);
base::RunLoop run_loop_1;
ambient_controller()->RequestAccessToken(base::BindLambdaForTesting(
[&](const std::string& gaia_id, const std::string& access_token_fetched) {
EXPECT_EQ(access_token_fetched, TestAmbientClient::kTestAccessToken);
std::move(closure).Run();
run_loop_1.Quit();
}));
EXPECT_FALSE(IsAccessTokenRequestPending());
run_loop_1.Run();
base::RunLoop run_loop_2;
// When token expired, another token request will get empty token.
constexpr base::TimeDelta kTokenRefreshDelay = base::Seconds(60);
task_environment()->FastForwardBy(kTokenRefreshDelay);
closure = base::MakeExpectedRunClosure(FROM_HERE);
ambient_controller()->RequestAccessToken(base::BindLambdaForTesting(
[&](const std::string& gaia_id, const std::string& access_token_fetched) {
EXPECT_TRUE(access_token_fetched.empty());
std::move(closure).Run();
run_loop_2.Quit();
}));
EXPECT_FALSE(IsAccessTokenRequestPending());
run_loop_2.Run();
// Clean up.
CloseAmbientScreen();
}
TEST_F(AmbientControllerTest, ShouldRetryRefreshAccessTokenAfterFailure) {
EXPECT_FALSE(IsAccessTokenRequestPending());
// Lock the screen will request a token.
LockScreen();
EXPECT_TRUE(IsAccessTokenRequestPending());
IssueAccessToken(/*is_empty=*/true);
EXPECT_FALSE(IsAccessTokenRequestPending());
// Token request automatically retry.
task_environment()->FastForwardBy(GetRefreshTokenDelay() * 1.1);
EXPECT_TRUE(IsAccessTokenRequestPending());
// Clean up.
CloseAmbientScreen();
}
TEST_F(AmbientControllerTest, ShouldRetryRefreshAccessTokenWithBackoffPolicy) {
EXPECT_FALSE(IsAccessTokenRequestPending());
// Lock the screen will request a token.
LockScreen();
EXPECT_TRUE(IsAccessTokenRequestPending());
IssueAccessToken(/*is_empty=*/true);
EXPECT_FALSE(IsAccessTokenRequestPending());
base::TimeDelta delay1 = GetRefreshTokenDelay();
task_environment()->FastForwardBy(delay1 * 1.1);
EXPECT_TRUE(IsAccessTokenRequestPending());
IssueAccessToken(/*is_empty=*/true);
EXPECT_FALSE(IsAccessTokenRequestPending());
base::TimeDelta delay2 = GetRefreshTokenDelay();
EXPECT_GT(delay2, delay1);
task_environment()->FastForwardBy(delay2 * 1.1);
EXPECT_TRUE(IsAccessTokenRequestPending());
// Clean up.
CloseAmbientScreen();
}
TEST_F(AmbientControllerTest, ShouldRetryRefreshAccessTokenOnlyThreeTimes) {
EXPECT_FALSE(IsAccessTokenRequestPending());
// Lock the screen will request a token.
LockScreen();
EXPECT_TRUE(IsAccessTokenRequestPending());
IssueAccessToken(/*is_empty=*/true);
EXPECT_FALSE(IsAccessTokenRequestPending());
// 1st retry.
task_environment()->FastForwardBy(GetRefreshTokenDelay() * 1.1);
EXPECT_TRUE(IsAccessTokenRequestPending());
IssueAccessToken(/*is_empty=*/true);
EXPECT_FALSE(IsAccessTokenRequestPending());
// 2nd retry.
task_environment()->FastForwardBy(GetRefreshTokenDelay() * 1.1);
EXPECT_TRUE(IsAccessTokenRequestPending());
IssueAccessToken(/*is_empty=*/true);
EXPECT_FALSE(IsAccessTokenRequestPending());
// 3rd retry.
task_environment()->FastForwardBy(GetRefreshTokenDelay() * 1.1);
EXPECT_TRUE(IsAccessTokenRequestPending());
IssueAccessToken(/*is_empty=*/true);
EXPECT_FALSE(IsAccessTokenRequestPending());
// Will not retry.
task_environment()->FastForwardBy(GetRefreshTokenDelay() * 1.1);
EXPECT_FALSE(IsAccessTokenRequestPending());
CloseAmbientScreen();
}
TEST_F(AmbientControllerTest,
CheckAcquireAndReleaseWakeLockWhenBatteryIsCharging) {
// Simulate a device being connected to a charger initially.
SetPowerStateCharging();
// Lock screen to start ambient mode, and flush the loop to ensure
// the acquire wake lock request has reached the wake lock provider.
LockScreen();
FastForwardToLockScreenTimeout();
FastForwardTiny();
EXPECT_EQ(1, GetNumOfActiveWakeLocks(
device::mojom::WakeLockType::kPreventDisplaySleep));
HideAmbientScreen();
base::RunLoop().RunUntilIdle();
EXPECT_EQ(0, GetNumOfActiveWakeLocks(
device::mojom::WakeLockType::kPreventDisplaySleep));
// Ambient screen showup again after inactivity.
FastForwardToLockScreenTimeout();
EXPECT_EQ(1, GetNumOfActiveWakeLocks(
device::mojom::WakeLockType::kPreventDisplaySleep));
// Unlock screen to exit ambient mode.
UnlockScreen();
base::RunLoop().RunUntilIdle();
EXPECT_EQ(0, GetNumOfActiveWakeLocks(
device::mojom::WakeLockType::kPreventDisplaySleep));
}
TEST_F(AmbientControllerTest,
CheckAcquireAndReleaseWakeLockWhenBatteryBatteryIsFullAndDischarging) {
SetPowerStateDischarging();
SetBatteryPercent(100.f);
SetExternalPowerConnected();
// Lock screen to start ambient mode, and flush the loop to ensure
// the acquire wake lock request has reached the wake lock provider.
LockScreen();
FastForwardToLockScreenTimeout();
FastForwardTiny();
EXPECT_EQ(1, GetNumOfActiveWakeLocks(
device::mojom::WakeLockType::kPreventDisplaySleep));
HideAmbientScreen();
base::RunLoop().RunUntilIdle();
EXPECT_EQ(0, GetNumOfActiveWakeLocks(
device::mojom::WakeLockType::kPreventDisplaySleep));
// Ambient screen showup again after inactivity.
FastForwardToLockScreenTimeout();
EXPECT_EQ(1, GetNumOfActiveWakeLocks(
device::mojom::WakeLockType::kPreventDisplaySleep));
// Unlock screen to exit ambient mode.
UnlockScreen();
base::RunLoop().RunUntilIdle();
EXPECT_EQ(0, GetNumOfActiveWakeLocks(
device::mojom::WakeLockType::kPreventDisplaySleep));
}
TEST_F(AmbientControllerTest,
CheckAcquireAndReleaseWakeLockWhenBatteryStateChanged) {
SetPowerStateDischarging();
SetExternalPowerConnected();
SetBatteryPercent(50.f);
// Lock screen to start ambient mode.
LockScreen();
FastForwardToLockScreenTimeout();
FastForwardTiny();
EXPECT_TRUE(ambient_controller()->IsShown());
// Should not acquire wake lock when device is not charging and with low
// battery.
EXPECT_EQ(0, GetNumOfActiveWakeLocks(
device::mojom::WakeLockType::kPreventDisplaySleep));
// Connect the device with a charger.
SetPowerStateCharging();
base::RunLoop().RunUntilIdle();
// Should acquire the wake lock when battery is charging.
EXPECT_EQ(1, GetNumOfActiveWakeLocks(
device::mojom::WakeLockType::kPreventDisplaySleep));
// Simulates a full battery.
SetBatteryPercent(100.f);
// Should keep the wake lock as the charger is still connected.
EXPECT_EQ(1, GetNumOfActiveWakeLocks(
device::mojom::WakeLockType::kPreventDisplaySleep));
// Disconnects the charger again.
SetPowerStateDischarging();
base::RunLoop().RunUntilIdle();
// Should keep the wake lock when battery is high.
EXPECT_EQ(1, GetNumOfActiveWakeLocks(
device::mojom::WakeLockType::kPreventDisplaySleep));
SetBatteryPercent(50.f);
base::RunLoop().RunUntilIdle();
// Should release the wake lock when battery is not charging and low.
EXPECT_EQ(0, GetNumOfActiveWakeLocks(
device::mojom::WakeLockType::kPreventDisplaySleep));
SetBatteryPercent(100.f);
base::RunLoop().RunUntilIdle();
// Should take the wake lock when battery is not charging and high.
EXPECT_EQ(1, GetNumOfActiveWakeLocks(
device::mojom::WakeLockType::kPreventDisplaySleep));
SetExternalPowerDisconnected();
base::RunLoop().RunUntilIdle();
// Should release the wake lock when power is not connected.
EXPECT_EQ(0, GetNumOfActiveWakeLocks(
device::mojom::WakeLockType::kPreventDisplaySleep));
// An unbalanced release should do nothing.
UnlockScreen();
EXPECT_EQ(0, GetNumOfActiveWakeLocks(
device::mojom::WakeLockType::kPreventDisplaySleep));
}
// TODO(cowmoo): find a way to simulate events to trigger |UserActivityDetector|
TEST_P(AmbientControllerTestForAnyUiSettings,
ShouldDismissContainerViewOnEvents) {
std::vector<std::unique_ptr<ui::Event>> events;
for (auto mouse_event_type : {ui::ET_MOUSE_PRESSED, ui::ET_MOUSE_MOVED}) {
events.emplace_back(std::make_unique<ui::MouseEvent>(
mouse_event_type, gfx::Point(), gfx::Point(), base::TimeTicks(),
ui::EF_LEFT_MOUSE_BUTTON, ui::EF_NONE));
}
events.emplace_back(std::make_unique<ui::MouseWheelEvent>(
gfx::Vector2d(), gfx::PointF(), gfx::PointF(), base::TimeTicks(),
ui::EF_MIDDLE_MOUSE_BUTTON, ui::EF_NONE));
events.emplace_back(std::make_unique<ui::ScrollEvent>(
ui::ET_SCROLL, gfx::PointF(), gfx::PointF(), base::TimeTicks(),
ui::EF_NONE, /*x_offset=*/0.0f,
/*y_offset=*/0.0f,
/*x_offset_ordinal=*/0.0f,
/*x_offset_ordinal=*/0.0f, /*finger_count=*/2));
events.emplace_back(std::make_unique<ui::TouchEvent>(
ui::ET_TOUCH_PRESSED, gfx::PointF(), gfx::PointF(), base::TimeTicks(),
ui::PointerDetails()));
for (const auto& event : events) {
ShowAmbientScreen();
FastForwardTiny();
EXPECT_TRUE(WidgetsVisible());
if (event.get()->IsMouseEvent()) {
ambient_controller()->OnMouseEvent(event.get()->AsMouseEvent());
} else if (event.get()->IsTouchEvent()) {
ambient_controller()->OnTouchEvent(event.get()->AsTouchEvent());
} else {
ambient_controller()->OnUserActivity(event.get());
}
FastForwardTiny();
EXPECT_TRUE(GetContainerViews().empty());
// Clean up.
CloseAmbientScreen();
}
}
TEST_P(AmbientControllerTestForAnyUiSettings, ShouldDismissAndThenComesBack) {
LockScreen();
FastForwardToLockScreenTimeout();
FastForwardTiny();
EXPECT_TRUE(WidgetsVisible());
GetEventGenerator()->PressLeftButton();
FastForwardTiny();
EXPECT_TRUE(GetContainerViews().empty());
FastForwardToLockScreenTimeout();
FastForwardTiny();
EXPECT_TRUE(WidgetsVisible());
}
TEST_P(AmbientControllerTestForAnyUiSettings,
ShouldDismissContainerViewOnKeyEvent) {
// Without user interaction, should show ambient mode.
ShowAmbientScreen();
EXPECT_TRUE(WidgetsVisible());
CloseAmbientScreen();
// When ambient is shown, OnUserActivity() should ignore key event.
ambient_controller()->ShowUi();
EXPECT_TRUE(ambient_controller()->IsShown());
// General key press will exit ambient mode.
// Simulate key press to close the widget.
PressAndReleaseKey(ui::VKEY_A);
EXPECT_FALSE(ambient_controller()->IsShown());
}
TEST_F(AmbientControllerTest,
ShouldDismissContainerViewOnKeyEventWhenLockScreenInBackground) {
GetSessionControllerClient()->SetShouldLockScreenAutomatically(true);
SetPowerStateCharging();
EXPECT_FALSE(ambient_controller()->IsShown());
// Should not lock the device and enter ambient mode when the screen is
// dimmed.
SetScreenIdleStateAndWait(/*dimmed=*/true, /*off=*/false);
EXPECT_FALSE(IsLocked());
EXPECT_TRUE(ambient_controller()->IsShown());
FastForwardToBackgroundLockScreenTimeout();
EXPECT_TRUE(IsLocked());
// Should not disrupt ongoing ambient mode.
EXPECT_TRUE(ambient_controller()->IsShown());
// General key press will exit ambient mode.
// Simulate key press to close the widget.
PressAndReleaseKey(ui::VKEY_A);
EXPECT_FALSE(ambient_controller()->IsShown());
}
TEST_F(AmbientControllerTest,
ShouldShowAmbientScreenWithLockscreenWhenScreenIsDimmed) {
GetSessionControllerClient()->SetShouldLockScreenAutomatically(true);
SetPowerStateCharging();
EXPECT_FALSE(ambient_controller()->IsShown());
// Should enter ambient mode when the screen is dimmed.
SetScreenIdleStateAndWait(/*dimmed=*/true, /*off=*/false);
EXPECT_FALSE(IsLocked());
EXPECT_TRUE(ambient_controller()->IsShown());
FastForwardToBackgroundLockScreenTimeout();
EXPECT_TRUE(IsLocked());
// Should not disrupt ongoing ambient mode.
EXPECT_TRUE(ambient_controller()->IsShown());
// Closes ambient for clean-up.
UnlockScreen();
EXPECT_FALSE(ambient_controller()->IsShown());
}
TEST_F(AmbientControllerTest,
ShouldShowAmbientScreenWithLockscreenWithNoisyPowerEvents) {
GetSessionControllerClient()->SetShouldLockScreenAutomatically(true);
SetPowerStateCharging();
EXPECT_FALSE(ambient_controller()->IsShown());
// Should enter ambient mode when the screen is dimmed.
SetScreenIdleStateAndWait(/*dimmed=*/true, /*off=*/false);
EXPECT_FALSE(IsLocked());
FastForwardTiny();
EXPECT_TRUE(ambient_controller()->IsShown());
FastForwardHalfLockScreenDelay();
SetPowerStateCharging();
FastForwardHalfLockScreenDelay();
SetPowerStateCharging();
EXPECT_TRUE(IsLocked());
// Should not disrupt ongoing ambient mode.
EXPECT_TRUE(ambient_controller()->IsShown());
// Closes ambient for clean-up.
UnlockScreen();
EXPECT_FALSE(ambient_controller()->IsShown());
}
TEST_F(AmbientControllerTest,
ShouldShowAmbientScreenWithoutLockscreenWhenScreenIsDimmed) {
GetSessionControllerClient()->SetShouldLockScreenAutomatically(true);
// When power is discharging, we do not lock the screen with ambient
// mode since we do not prevent the device go to sleep which will natually
// lock the device.
SetPowerStateDischarging();
EXPECT_FALSE(ambient_controller()->IsShown());
// Should not lock the device but still enter ambient mode when the screen is
// dimmed.
SetScreenIdleStateAndWait(/*dimmed=*/true, /*off=*/false);
EXPECT_FALSE(IsLocked());
EXPECT_TRUE(ambient_controller()->IsShown());
FastForwardToLockScreenTimeout();
FastForwardTiny();
EXPECT_TRUE(ambient_controller()->IsShown());
FastForwardToBackgroundLockScreenTimeout();
EXPECT_FALSE(IsLocked());
// Closes ambient for clean-up.
CloseAmbientScreen();
}
TEST_F(AmbientControllerTest, ShouldShowAmbientScreenWhenScreenIsDimmed) {
GetSessionControllerClient()->SetShouldLockScreenAutomatically(false);
SetPowerStateCharging();
EXPECT_FALSE(ambient_controller()->IsShown());
// Should not lock the device but enter ambient mode when the screen is
// dimmed.
SetScreenIdleStateAndWait(/*dimmed=*/true, /*off=*/false);
EXPECT_FALSE(IsLocked());
FastForwardTiny();
EXPECT_TRUE(ambient_controller()->IsShown());
FastForwardToBackgroundLockScreenTimeout();
EXPECT_FALSE(IsLocked());
// Closes ambient for clean-up.
CloseAmbientScreen();
}
TEST_F(AmbientControllerTest, HandlesPreviousImageFailuresWithLockScreen) {
// Simulate failures to download FIFE urls. Ambient mode should close and
// remember the old failure.
SetDownloadPhotoData("");
LockScreen();
FastForwardToLockScreenTimeout();
FastForwardTiny();
ASSERT_TRUE(ambient_controller()->IsShown());
AmbientUiVisibilityBarrier ambient_closed_barrier(
AmbientUiVisibility::kClosed);
ambient_closed_barrier.WaitWithTimeout(base::Seconds(15));
ASSERT_FALSE(ambient_controller()->IsShown());
UnlockScreen();
// Now simulate FIFE downloads starting to work again. The device should be
// able to enter ambient mode.
ClearDownloadPhotoData();
LockScreen();
FastForwardToLockScreenTimeout();
FastForwardTiny();
ASSERT_TRUE(ambient_controller()->IsShown());
}
TEST_F(AmbientControllerTest, HandlesPreviousImageFailuresWithDimmedScreen) {
GetSessionControllerClient()->SetShouldLockScreenAutomatically(false);
SetPowerStateCharging();
// Simulate failures to download FIFE urls. Ambient mode should close and
// remember the old failure.
SetDownloadPhotoData("");
SetScreenIdleStateAndWait(/*is_screen_dimmed=*/true, /*is_off=*/false);
FastForwardTiny();
ASSERT_TRUE(ambient_controller()->IsShown());
AmbientUiVisibilityBarrier ambient_closed_barrier(
AmbientUiVisibility::kClosed);
ambient_closed_barrier.WaitWithTimeout(base::Seconds(15));
ASSERT_FALSE(ambient_controller()->IsShown());
SetScreenIdleStateAndWait(/*is_screen_dimmed=*/false, /*is_off=*/false);
// Usually would enter ambient mode when the screen is dimmed, but this time
// it shouldn't because of the previous image failures.
SetScreenIdleStateAndWait(/*is_screen_dimmed=*/true, /*is_off=*/false);
FastForwardTiny();
ASSERT_FALSE(ambient_controller()->IsShown());
SetScreenIdleStateAndWait(/*is_screen_dimmed=*/false, /*is_off=*/false);
// Now simulate FIFE downloads starting to work again. The device should be
// able to enter ambient mode.
ClearDownloadPhotoData();
SetScreenIdleStateAndWait(/*is_screen_dimmed=*/true, /*is_off=*/false);
FastForwardTiny();
ASSERT_TRUE(ambient_controller()->IsShown());
// Closes ambient for clean-up.
CloseAmbientScreen();
}
TEST_F(AmbientControllerTest, ShouldHideAmbientScreenWhenDisplayIsOff) {
GetSessionControllerClient()->SetShouldLockScreenAutomatically(false);
EXPECT_FALSE(ambient_controller()->IsShown());
// Should not lock the device and enter ambient mode when the screen is
// dimmed.
SetScreenIdleStateAndWait(/*dimmed=*/true, /*off=*/false);
EXPECT_FALSE(IsLocked());
FastForwardTiny();
EXPECT_TRUE(ambient_controller()->IsShown());
// Should dismiss ambient mode screen.
SetScreenIdleStateAndWait(/*dimmed=*/true, /*off=*/true);
FastForwardTiny();
EXPECT_FALSE(ambient_controller()->IsShown());
// Screen back on again, should not have ambient screen.
SetScreenIdleStateAndWait(/*dimmed=*/false, /*off=*/false);
FastForwardTiny();
EXPECT_FALSE(ambient_controller()->IsShown());
}
TEST_F(AmbientControllerTest,
ShouldHideAmbientScreenWhenDisplayIsOffThenComesBackWithLockScreen) {
GetSessionControllerClient()->SetShouldLockScreenAutomatically(true);
SetPowerStateCharging();
EXPECT_FALSE(ambient_controller()->IsShown());
// Should not lock the device and enter ambient mode when the screen is
// dimmed.
SetScreenIdleStateAndWait(/*dimmed=*/true, /*off=*/false);
EXPECT_FALSE(IsLocked());
FastForwardToLockScreenTimeout();
FastForwardTiny();
EXPECT_TRUE(ambient_controller()->IsShown());
FastForwardToBackgroundLockScreenTimeout();
EXPECT_TRUE(IsLocked());
// Should dismiss ambient mode screen.
SetScreenIdleStateAndWait(/*dimmed=*/true, /*off=*/true);
FastForwardTiny();
EXPECT_FALSE(ambient_controller()->IsShown());
// Screen back on again, should not have ambient screen, but still has lock
// screen.
SetScreenIdleStateAndWait(/*dimmed=*/false, /*off=*/false);
EXPECT_TRUE(IsLocked());
EXPECT_FALSE(ambient_controller()->IsShown());
FastForwardToLockScreenTimeout();
FastForwardTiny();
EXPECT_TRUE(ambient_controller()->IsShown());
}
TEST_F(AmbientControllerTest,
ShouldHideAmbientScreenWhenDisplayIsOffAndNotStartWhenLockScreen) {
GetSessionControllerClient()->SetShouldLockScreenAutomatically(true);
SetPowerStateDischarging();
EXPECT_FALSE(ambient_controller()->IsShown());
// Should not lock the device and enter ambient mode when the screen is
// dimmed.
SetScreenIdleStateAndWait(/*dimmed=*/true, /*off=*/false);
EXPECT_FALSE(IsLocked());
FastForwardTiny();
EXPECT_TRUE(ambient_controller()->IsShown());
// Should not lock the device because the device is not charging.
FastForwardToBackgroundLockScreenTimeout();
EXPECT_FALSE(IsLocked());
// Should dismiss ambient mode screen.
SetScreenIdleStateAndWait(/*dimmed=*/true, /*off=*/true);
FastForwardTiny();
EXPECT_FALSE(ambient_controller()->IsShown());
// Lock screen will not start ambient mode.
LockScreen();
EXPECT_TRUE(IsLocked());
FastForwardToLockScreenTimeout();
FastForwardTiny();
EXPECT_FALSE(ambient_controller()->IsShown());
// Screen back on again, should not have ambient screen, but still has lock
// screen.
SetScreenIdleStateAndWait(/*dimmed=*/false, /*off=*/false);
EXPECT_TRUE(IsLocked());
EXPECT_FALSE(ambient_controller()->IsShown());
FastForwardToLockScreenTimeout();
FastForwardTiny();
EXPECT_TRUE(ambient_controller()->IsShown());
}
TEST_F(AmbientControllerTest, HandlesPhotoDownloadOutage) {
SetDownloadPhotoData("");
LockScreen();
FastForwardToLockScreenTimeout();
ASSERT_TRUE(ambient_controller()->IsShown());
AmbientUiVisibilityBarrier ambient_closed_barrier(
AmbientUiVisibility::kClosed);
ambient_closed_barrier.WaitWithTimeout(base::Seconds(15));
EXPECT_FALSE(ambient_controller()->IsShown());
}
TEST_P(AmbientControllerTestForAnyUiSettings, HideCursor) {
auto* cursor_manager = Shell::Get()->cursor_manager();
LockScreen();
cursor_manager->ShowCursor();
EXPECT_TRUE(cursor_manager->IsCursorVisible());
FastForwardToLockScreenTimeout();
FastForwardTiny();
EXPECT_FALSE(GetContainerViews().empty());
EXPECT_EQ(AmbientUiModel::Get()->ui_visibility(),
AmbientUiVisibility::kShown);
EXPECT_TRUE(ambient_controller()->IsShown());
EXPECT_FALSE(cursor_manager->IsCursorVisible());
// Clean up.
UnlockScreen();
EXPECT_FALSE(ambient_controller()->IsShown());
}
TEST_P(AmbientControllerTestForAnyUiSettings, ShowsOnMultipleDisplays) {
UpdateDisplay("800x600,800x600");
FastForwardTiny();
ShowAmbientScreen();
auto* screen = display::Screen::GetScreen();
EXPECT_EQ(screen->GetNumDisplays(), 2);
EXPECT_EQ(GetContainerViews().size(), 2u);
AmbientViewID expected_child_view_id;
switch (GetParam().theme()) {
case AmbientTheme::kVideo:
expected_child_view_id = kAmbientVideoWebView;
break;
case AmbientTheme::kSlideshow:
expected_child_view_id = AmbientViewID::kAmbientPhotoView;
break;
case AmbientTheme::kFeelTheBreeze:
case AmbientTheme::kFloatOnBy:
expected_child_view_id = AmbientViewID::kAmbientAnimationView;
break;
}
EXPECT_TRUE(GetContainerViews().front()->GetViewByID(expected_child_view_id));
EXPECT_TRUE(GetContainerViews().back()->GetViewByID(expected_child_view_id));
// Check that each root controller has an ambient widget.
for (auto* ctrl : RootWindowController::root_window_controllers())
EXPECT_TRUE(ctrl->ambient_widget_for_testing() &&
ctrl->ambient_widget_for_testing()->IsVisible());
}
TEST_P(AmbientControllerTestForAnyUiSettings, RespondsToDisplayAdded) {
// UpdateDisplay triggers a rogue MouseEvent that cancels Ambient mode when
// testing with Xvfb. A corresponding MouseEvent is not fired on a real device
// when an external display is added. Ignore this MouseEvent for testing.
// Store the old |ShouldIgnoreNativePlatformEvents| value and reset it at the
// end of the test.
bool old_should_ignore_events =
ui::PlatformEventSource::ShouldIgnoreNativePlatformEvents();
ui::PlatformEventSource::SetIgnoreNativePlatformEvents(true);
UpdateDisplay("800x600");
ShowAmbientScreen();
auto* screen = display::Screen::GetScreen();
EXPECT_EQ(screen->GetNumDisplays(), 1);
EXPECT_EQ(GetContainerViews().size(), 1u);
UpdateDisplay("800x600,800x600");
FastForwardTiny();
EXPECT_TRUE(WidgetsVisible());
EXPECT_EQ(screen->GetNumDisplays(), 2);
EXPECT_EQ(GetContainerViews().size(), 2u);
for (auto* ctrl : RootWindowController::root_window_controllers())
EXPECT_TRUE(ctrl->ambient_widget_for_testing() &&
ctrl->ambient_widget_for_testing()->IsVisible());
ui::PlatformEventSource::SetIgnoreNativePlatformEvents(
old_should_ignore_events);
}
TEST_P(AmbientControllerTestForAnyUiSettings, HandlesDisplayRemoved) {
UpdateDisplay("800x600,800x600");
FastForwardTiny();
ShowAmbientScreen();
auto* screen = display::Screen::GetScreen();
EXPECT_EQ(screen->GetNumDisplays(), 2);
EXPECT_EQ(GetContainerViews().size(), 2u);
EXPECT_TRUE(WidgetsVisible());
// Changing to one screen will destroy the widget on the non-primary screen.
UpdateDisplay("800x600");
FastForwardTiny();
EXPECT_EQ(screen->GetNumDisplays(), 1);
EXPECT_EQ(GetContainerViews().size(), 1u);
EXPECT_TRUE(WidgetsVisible());
}
TEST_F(AmbientControllerTest, ClosesAmbientBeforeSuspend) {
LockScreen();
FastForwardToLockScreenTimeout();
EXPECT_TRUE(ambient_controller()->IsShown());
SimulateSystemSuspendAndWait(power_manager::SuspendImminent::Reason::
SuspendImminent_Reason_LID_CLOSED);
EXPECT_FALSE(ambient_controller()->IsShown());
FastForwardToLockScreenTimeout();
// Ambient mode should not resume until SuspendDone is received.
EXPECT_FALSE(ambient_controller()->IsShown());
}
TEST_F(AmbientControllerTest, RestartsAmbientAfterSuspend) {
LockScreen();
FastForwardToLockScreenTimeout();
EXPECT_TRUE(ambient_controller()->IsShown());
SimulateSystemSuspendAndWait(
power_manager::SuspendImminent::Reason::SuspendImminent_Reason_IDLE);
EXPECT_FALSE(ambient_controller()->IsShown());
// This call should be blocked by prior |SuspendImminent| until |SuspendDone|.
ambient_controller()->ShowUi();
EXPECT_FALSE(ambient_controller()->IsShown());
SimulateSystemResumeAndWait();
FastForwardToLockScreenTimeout();
EXPECT_TRUE(ambient_controller()->IsShown());
}
TEST_F(AmbientControllerTest, ObservesPrefsWhenAmbientEnabled) {
SetAmbientModeEnabled(false);
// This pref is always observed.
EXPECT_TRUE(IsPrefObserved(ambient::prefs::kAmbientModeEnabled));
std::vector<std::string> other_prefs{
ambient::prefs::kAmbientModeLockScreenInactivityTimeoutSeconds,
ambient::prefs::kAmbientModeLockScreenBackgroundTimeoutSeconds,
ambient::prefs::kAmbientModePhotoRefreshIntervalSeconds};
for (auto& pref_name : other_prefs)
EXPECT_FALSE(IsPrefObserved(pref_name));
SetAmbientModeEnabled(true);
EXPECT_TRUE(IsPrefObserved(ambient::prefs::kAmbientModeEnabled));
for (auto& pref_name : other_prefs)
EXPECT_TRUE(IsPrefObserved(pref_name));
}
TEST_F(AmbientControllerTest, BindsObserversWhenAmbientEnabled) {
auto* ctrl = ambient_controller();
SetAmbientModeEnabled(false);
// SessionObserver must always be observing to detect when user pref service
// is started.
EXPECT_TRUE(ctrl->session_observer_.IsObserving());
EXPECT_FALSE(AreSessionSpecificObserversBound());
SetAmbientModeEnabled(true);
// Session observer should still be observing.
EXPECT_TRUE(ctrl->session_observer_.IsObserving());
EXPECT_TRUE(AreSessionSpecificObserversBound());
}
TEST_F(AmbientControllerTest, SwitchActiveUsersDoesNotDoubleBindObservers) {
ClearLogin();
SimulateUserLogin(kUser1);
SetAmbientModeEnabled(true);
TestSessionControllerClient* session = GetSessionControllerClient();
// Observers are bound for primary user with Ambient mode enabled.
EXPECT_TRUE(AreSessionSpecificObserversBound());
EXPECT_TRUE(IsPrefObserved(ambient::prefs::kAmbientModeEnabled));
// Observers are still bound when secondary user logs in.
SimulateUserLogin(kUser2);
EXPECT_TRUE(AreSessionSpecificObserversBound());
EXPECT_TRUE(IsPrefObserved(ambient::prefs::kAmbientModeEnabled));
// Observers are not re-bound for primary user when session is active.
session->SwitchActiveUser(AccountId::FromUserEmail(kUser1));
EXPECT_TRUE(AreSessionSpecificObserversBound());
EXPECT_TRUE(IsPrefObserved(ambient::prefs::kAmbientModeEnabled));
// Switch back to secondary user.
session->SwitchActiveUser(AccountId::FromUserEmail(kUser2));
}
TEST_F(AmbientControllerTest, BindsObserversWhenAmbientOn) {
auto* ctrl = ambient_controller();
LockScreen();
// Start monitoring user activity on hidden ui.
EXPECT_TRUE(ctrl->user_activity_observer_.IsObserving());
// Do not monitor power status yet.
EXPECT_FALSE(ctrl->power_status_observer_.IsObserving());
FastForwardToLockScreenTimeout();
EXPECT_TRUE(ctrl->user_activity_observer_.IsObserving());
EXPECT_TRUE(ctrl->power_status_observer_.IsObserving());
UnlockScreen();
EXPECT_FALSE(ctrl->user_activity_observer_.IsObserving());
EXPECT_FALSE(ctrl->power_status_observer_.IsObserving());
}
TEST_P(AmbientControllerTestForAnyUiSettings,
ShowDismissAmbientScreenUponAssistantQuery) {
// Without user interaction, should show ambient mode.
ShowAmbientScreen();
EXPECT_TRUE(ambient_controller()->IsShown());
// Trigger Assistant interaction.
static_cast<AssistantInteractionControllerImpl*>(
AssistantInteractionController::Get())
->OnInteractionStarted(AssistantInteractionMetadata());
base::RunLoop().RunUntilIdle();
// Ambient screen should dismiss.
EXPECT_TRUE(GetContainerViews().empty());
EXPECT_FALSE(ambient_controller()->IsShown());
}
// For all test cases that depend on ash ambient resources (lottie files, image
// assets, etc) being present to run.
#if BUILDFLAG(HAS_ASH_AMBIENT_ANIMATION_RESOURCES)
#define ANIMATION_TEST_WITH_RESOURCES(test_case_name) test_case_name
#else
#define ANIMATION_TEST_WITH_RESOURCES(test_case_name) DISABLED_##test_case_name
#endif // BUILDFLAG(HAS_ASH_AMBIENT_ANIMATION_RESOURCES)
TEST_F(AmbientControllerTest,
ANIMATION_TEST_WITH_RESOURCES(RendersCorrectView)) {
SetAmbientTheme(AmbientTheme::kFeelTheBreeze);
LockScreen();
FastForwardToLockScreenTimeout();
FastForwardTiny();
ASSERT_TRUE(GetContainerView());
EXPECT_FALSE(
GetContainerView()->GetViewByID(AmbientViewID::kAmbientPhotoView));
EXPECT_TRUE(
GetContainerView()->GetViewByID(AmbientViewID::kAmbientAnimationView));
UnlockScreen();
SetAmbientTheme(AmbientTheme::kSlideshow);
LockScreen();
FastForwardToLockScreenTimeout();
FastForwardTiny();
ASSERT_TRUE(GetContainerView());
EXPECT_TRUE(
GetContainerView()->GetViewByID(AmbientViewID::kAmbientPhotoView));
EXPECT_FALSE(
GetContainerView()->GetViewByID(AmbientViewID::kAmbientAnimationView));
UnlockScreen();
SetAmbientTheme(AmbientTheme::kFeelTheBreeze);
LockScreen();
FastForwardToLockScreenTimeout();
FastForwardTiny();
ASSERT_TRUE(GetContainerView());
EXPECT_FALSE(
GetContainerView()->GetViewByID(AmbientViewID::kAmbientPhotoView));
EXPECT_TRUE(
GetContainerView()->GetViewByID(AmbientViewID::kAmbientAnimationView));
}
TEST_F(AmbientControllerTest,
ANIMATION_TEST_WITH_RESOURCES(ClearsCacheWhenSwitchingThemes)) {
SetAmbientTheme(AmbientTheme::kSlideshow);
LockScreen();
FastForwardToLockScreenTimeout();
FastForwardTiny();
ASSERT_TRUE(GetContainerView());
ASSERT_FALSE(GetCachedFiles().empty());
UnlockScreen();
SetAmbientTheme(AmbientTheme::kFeelTheBreeze);
// Mimic a network outage where no photos can be downloaded. Since the cache
// should have been cleared when we switched ambient animation themes, the
// UI shouldn't start with a photo cached during slideshow mode.
SetDownloadPhotoData(/*data=*/"");
LockScreen();
FastForwardToLockScreenTimeout();
FastForwardTiny();
EXPECT_FALSE(GetContainerView());
EXPECT_TRUE(GetCachedFiles().empty());
}
TEST_P(AmbientControllerTestForAnyUiSettings, MetricsEngagementTime) {
// TODO(esum): Find a better way of fast forwarding time for lottie animations
// in unit tests. Currently, the whole compositor stack is being used in this
// test harness and there is no good way to control the frame rate, so
// FastForwardBy() blocks for long periods of time. Do not make this value
// too high, or the test is at risk of timing out.
constexpr base::TimeDelta kExpectedEngagementTime = base::Milliseconds(100);
base::HistogramTester histogram_tester;
Shell::Get()->tablet_mode_controller()->SetEnabledForTest(false);
LockScreen();
// Unlike other tests, the exact amount of time we spend in ambient mode
// matters to write the correct test expectation. So fast forward by the
// exact amount needed to trigger ambient mode.
// (FastForwardToLockScreenTimeout() adds on a little buffer to the timeout)
task_environment()->FastForwardBy(ambient_controller()
->ambient_ui_model()
->lock_screen_inactivity_timeout());
ASSERT_TRUE(ambient_controller()->IsShown());
task_environment()->FastForwardBy(kExpectedEngagementTime);
UnlockScreen();
ASSERT_FALSE(ambient_controller()->IsShown());
histogram_tester.ExpectTimeBucketCount(
"Ash.AmbientMode.EngagementTime.ClamshellMode", kExpectedEngagementTime,
1);
histogram_tester.ExpectTimeBucketCount(
base::StrCat(
{"Ash.AmbientMode.EngagementTime.", ToString(GetParam().theme())}),
kExpectedEngagementTime, 1);
// Now do the same sequence in tablet mode.
Shell::Get()->tablet_mode_controller()->SetEnabledForTest(true);
LockScreen();
task_environment()->FastForwardBy(ambient_controller()
->ambient_ui_model()
->lock_screen_inactivity_timeout());
ASSERT_TRUE(ambient_controller()->IsShown());
task_environment()->FastForwardBy(kExpectedEngagementTime);
UnlockScreen();
ASSERT_FALSE(ambient_controller()->IsShown());
histogram_tester.ExpectTimeBucketCount(
"Ash.AmbientMode.EngagementTime.TabletMode", kExpectedEngagementTime, 1);
histogram_tester.ExpectTimeBucketCount(
base::StrCat(
{"Ash.AmbientMode.EngagementTime.", ToString(GetParam().theme())}),
kExpectedEngagementTime, 2);
}
TEST_P(AmbientControllerTestForAnyUiSettings, MetricsStartupTime) {
base::HistogramTester histogram_tester;
LockScreen();
FastForwardToLockScreenTimeout();
FastForwardTiny();
ASSERT_TRUE(ambient_controller()->IsShown());
histogram_tester.ExpectTotalCount(
base::StrCat(
{"Ash.AmbientMode.StartupTime.", ToString(GetParam().theme())}),
1);
UnlockScreen();
ASSERT_FALSE(ambient_controller()->IsShown());
LockScreen();
FastForwardToLockScreenTimeout();
FastForwardTiny();
ASSERT_TRUE(ambient_controller()->IsShown());
histogram_tester.ExpectTotalCount(
base::StrCat(
{"Ash.AmbientMode.StartupTime.", ToString(GetParam().theme())}),
2);
}
TEST_F(AmbientControllerTest,
ANIMATION_TEST_WITH_RESOURCES(MetricsStartupTimeSuspendAfterTimeMax)) {
SetAmbientTheme(AmbientTheme::kSlideshow);
base::HistogramTester histogram_tester;
LockScreen();
FastForwardToLockScreenTimeout();
task_environment()->FastForwardBy(ambient::kMetricsStartupTimeMax);
FastForwardTiny();
ASSERT_TRUE(ambient_controller()->IsShown());
SimulateSystemSuspendAndWait(power_manager::SuspendImminent::Reason::
SuspendImminent_Reason_LID_CLOSED);
ASSERT_FALSE(ambient_controller()->IsShown());
histogram_tester.ExpectTotalCount("Ash.AmbientMode.StartupTime.SlideShow", 1);
UnlockScreen();
}
TEST_F(AmbientControllerTest,
ANIMATION_TEST_WITH_RESOURCES(MetricsStartupTimeScreenOffAfterTimeMax)) {
SetAmbientTheme(AmbientTheme::kSlideshow);
base::HistogramTester histogram_tester;
LockScreen();
FastForwardToLockScreenTimeout();
task_environment()->FastForwardBy(ambient::kMetricsStartupTimeMax);
FastForwardTiny();
ASSERT_TRUE(ambient_controller()->IsShown());
SetScreenIdleStateAndWait(/*dimmed=*/true, /*off=*/true);
ASSERT_FALSE(ambient_controller()->IsShown());
histogram_tester.ExpectTotalCount("Ash.AmbientMode.StartupTime.SlideShow", 1);
UnlockScreen();
}
TEST_P(AmbientControllerTestForAnyUiSettings, MetricsStartupTimeFailedToStart) {
switch (GetParam().theme()) {
case AmbientTheme::kVideo:
// Video themes have no dependency on backend photos, so we cannot test
// failure to start ambient mode by simulating an IMAX outage. Video
// themes should always start unless there is a design oversight or bug.
GTEST_SKIP();
case AmbientTheme::kSlideshow:
case AmbientTheme::kFeelTheBreeze:
case AmbientTheme::kFloatOnBy:
break;
}
// Simulate IMAX outage that doesn't return any photos.
backend_controller()->SetFetchScreenUpdateInfoResponseSize(0);
base::HistogramTester histogram_tester;
LockScreen();
FastForwardToLockScreenTimeout();
task_environment()->FastForwardBy(base::Minutes(1));
ASSERT_TRUE(GetContainerViews().empty());
UnlockScreen();
histogram_tester.ExpectUniqueTimeSample(
base::StrCat({"Ash.AmbientMode.StartupTime.", GetParam().ToString()}),
base::Minutes(1), 1);
}
TEST_F(AmbientControllerTest, ShouldStartScreenSaverPreview) {
ASSERT_EQ(0,
user_action_tester_.GetActionCount(kScreenSaverPreviewUserAction));
ambient_controller()->StartScreenSaverPreview();
FastForwardToLockScreenTimeout();
FastForwardTiny();
EXPECT_TRUE(ambient_controller()->IsShown());
EXPECT_FALSE(IsLocked());
EXPECT_EQ(1,
user_action_tester_.GetActionCount(kScreenSaverPreviewUserAction));
}
TEST_F(AmbientControllerTest,
ShouldNotDismissScreenSaverPreviewOnUserActivity) {
ambient_controller()->StartScreenSaverPreview();
EXPECT_TRUE(ambient_controller()->IsShown());
ui::MouseEvent mouse_event(ui::ET_MOUSE_RELEASED, gfx::Point(), gfx::Point(),
base::TimeTicks(), ui::EF_NONE, ui::EF_NONE);
ambient_controller()->OnUserActivity(&mouse_event);
FastForwardTiny();
EXPECT_TRUE(ambient_controller()->IsShown());
}
TEST_F(AmbientControllerTest, ShouldDismissScreenSaverPreviewOnKeyReleased) {
ambient_controller()->StartScreenSaverPreview();
EXPECT_TRUE(ambient_controller()->IsShown());
GetEventGenerator()->ReleaseKey(ui::VKEY_A, ui::EF_NONE);
EXPECT_TRUE(ambient_controller()->IsShown());
GetEventGenerator()->PressKey(ui::VKEY_A, ui::EF_NONE);
EXPECT_FALSE(ambient_controller()->IsShown());
}
TEST_F(AmbientControllerTest,
ShouldNotDismissScreenSaverPreviewOnSomeMouseEvents) {
ambient_controller()->StartScreenSaverPreview();
EXPECT_TRUE(ambient_controller()->IsShown());
GetEventGenerator()->MoveMouseWheel(10, 10);
EXPECT_TRUE(ambient_controller()->IsShown());
GetEventGenerator()->SendMouseEnter();
EXPECT_TRUE(ambient_controller()->IsShown());
GetEventGenerator()->SendMouseExit();
EXPECT_TRUE(ambient_controller()->IsShown());
}
TEST_F(AmbientControllerTest, ShouldDismissScreenSaverPreviewOnMouseClick) {
ambient_controller()->StartScreenSaverPreview();
EXPECT_TRUE(ambient_controller()->IsShown());
GetEventGenerator()->ClickLeftButton();
EXPECT_FALSE(ambient_controller()->IsShown());
ambient_controller()->StartScreenSaverPreview();
EXPECT_TRUE(ambient_controller()->IsShown());
GetEventGenerator()->ClickRightButton();
EXPECT_FALSE(ambient_controller()->IsShown());
}
TEST_F(AmbientControllerTest, MaybeDismissUIOnMouseMove) {
ambient_controller()->StartScreenSaverPreview();
EXPECT_TRUE(ambient_controller()->IsShown());
GetEventGenerator()->MoveMouseTo(gfx::Point(5, 5), /*count=*/2);
EXPECT_TRUE(ambient_controller()->IsShown());
task_environment()->FastForwardBy(kDismissPreviewOnMouseMoveDelay);
FastForwardTiny();
GetEventGenerator()->MoveMouseTo(gfx::Point(5, 5), /*count=*/2);
EXPECT_FALSE(ambient_controller()->IsShown());
}
TEST_F(AmbientControllerTest, ShouldDismissScreenSaverPreviewOnTouch) {
ambient_controller()->StartScreenSaverPreview();
EXPECT_TRUE(ambient_controller()->IsShown());
GetEventGenerator()->PressTouch();
EXPECT_FALSE(ambient_controller()->IsShown());
ambient_controller()->StartScreenSaverPreview();
EXPECT_TRUE(ambient_controller()->IsShown());
GetEventGenerator()->ReleaseTouch();
EXPECT_FALSE(ambient_controller()->IsShown());
}
class AmbientControllerForManagedScreensaver : public AmbientAshTestBase {
protected:
void SetUp() override {
scoped_feature_list_.InitAndEnableFeature(
ash::features::kAmbientModeManagedScreensaver);
AmbientAshTestBase::SetUp();
GetSessionControllerClient()->set_show_lock_screen_views(true);
CreateTestData();
}
void CreateTestData() {
bool success = temp_dir_.CreateUniqueTempDir();
ASSERT_TRUE(success);
base::FilePath image_1 =
temp_dir_.GetPath().Append(FILE_PATH_LITERAL("IMAGE_1.jpg"));
CreateTestImageJpegFile(image_1, 4, 4, SK_ColorRED);
base::FilePath image_2 =
temp_dir_.GetPath().Append(FILE_PATH_LITERAL("IMAGE_2.jpg"));
CreateTestImageJpegFile(image_2, 8, 8, SK_ColorGREEN);
image_file_paths_.push_back(image_1);
image_file_paths_.push_back(image_2);
}
void SimulateScreensaverStart() {
LockScreen();
FastForwardToLockScreenTimeout();
FastForwardTiny();
EXPECT_TRUE(ambient_controller()->IsShown());
}
base::test::ScopedFeatureList scoped_feature_list_;
InProcessImageDecoder decoder_;
std::vector<base::FilePath> image_file_paths_;
base::ScopedTempDir temp_dir_;
};
TEST_F(AmbientControllerForManagedScreensaver,
ScreensaverIsShownWithEnoughImages) {
SetAmbientModeManagedScreensaverEnabled(true);
managed_photo_controller()->UpdateImageFilePaths(image_file_paths_);
SimulateScreensaverStart();
ASSERT_TRUE(GetContainerView());
EXPECT_TRUE(
GetContainerView()->GetViewByID(AmbientViewID::kAmbientPhotoView));
GetEventGenerator()->ClickLeftButton();
EXPECT_FALSE(ambient_controller()->IsShown());
FastForwardToLockScreenTimeout();
EXPECT_TRUE(ambient_controller()->IsShown());
UnlockScreen();
EXPECT_FALSE(ambient_controller()->IsShown());
ASSERT_FALSE(GetContainerView());
}
TEST_F(AmbientControllerForManagedScreensaver,
ScreensaverIsNotShownWithoutImages) {
SetAmbientModeManagedScreensaverEnabled(true);
LockScreen();
FastForwardToLockScreenTimeout();
FastForwardTiny();
EXPECT_TRUE(ambient_controller()->IsShown());
ASSERT_FALSE(GetContainerView());
UnlockScreen();
EXPECT_FALSE(ambient_controller()->IsShown());
}
TEST_F(AmbientControllerForManagedScreensaver,
UiLauncherIsNullWhenManagedAmbientModeIsDisabled) {
SetAmbientModeEnabled(false);
SetAmbientModeManagedScreensaverEnabled(false);
ASSERT_FALSE(ambient_controller()->ambient_ui_launcher());
EXPECT_FALSE(ambient_controller()->IsShown());
}
TEST_F(AmbientControllerForManagedScreensaver,
DisablingManagedAmbientModeFallsbackToUserAmbientModeIfEnabled) {
SetAmbientModeEnabled(true);
SetAmbientModeManagedScreensaverEnabled(true);
managed_photo_controller()->UpdateImageFilePaths(image_file_paths_);
SimulateScreensaverStart();
ASSERT_TRUE(GetContainerView());
EXPECT_TRUE(
GetContainerView()->GetViewByID(AmbientViewID::kAmbientPhotoView));
SetAmbientModeManagedScreensaverEnabled(false);
DisableBackupCacheDownloads();
UnlockScreen();
LockScreen();
FastForwardToLockScreenTimeout();
FastForwardTiny();
EXPECT_TRUE(ambient_controller()->IsShown());
ASSERT_TRUE(GetContainerView());
EXPECT_TRUE(
GetContainerView()->GetViewByID(AmbientViewID::kAmbientPhotoView));
UnlockScreen();
EXPECT_FALSE(ambient_controller()->IsShown());
}
TEST_F(AmbientControllerForManagedScreensaver,
LaunchingManagedAmbientModeAfterAmbientModeWorksAsExpected) {
SetAmbientModeEnabled(/*enabled=*/true);
ASSERT_FALSE(ambient_controller()->ambient_ui_launcher());
SetAmbientModeManagedScreensaverEnabled(/*enabled=*/true);
ASSERT_TRUE(ambient_controller()->ambient_ui_launcher());
managed_photo_controller()->UpdateImageFilePaths(image_file_paths_);
SimulateScreensaverStart();
UnlockScreen();
EXPECT_FALSE(ambient_controller()->IsShown());
}
TEST_F(AmbientControllerForManagedScreensaver,
LaunchingAmbientModeAfterManagedAmbientModeWorksAsExpected) {
SetAmbientModeEnabled(/*enabled=*/false);
SetAmbientModeManagedScreensaverEnabled(/*enabled=*/true);
SetAmbientModeEnabled(/*enabled=*/true);
managed_photo_controller()->UpdateImageFilePaths(image_file_paths_);
SimulateScreensaverStart();
UnlockScreen();
EXPECT_FALSE(ambient_controller()->IsShown());
}
TEST_F(AmbientControllerForManagedScreensaver, PrefObserverUpdatesUiModel) {
SetAmbientModeManagedScreensaverEnabled(/*enabled=*/true);
ASSERT_TRUE(ambient_controller()->ambient_ui_launcher());
PrefService* pref_service =
Shell::Get()->session_controller()->GetActivePrefService();
AmbientUiModel* ui_model = ambient_controller()->ambient_ui_model();
constexpr size_t kExpectedIdleTimeout = 55;
constexpr size_t kExpectedPhotoRefreshInterval = 77;
pref_service->SetInteger(
ambient::prefs::kAmbientModeManagedScreensaverIdleTimeoutSeconds,
kExpectedIdleTimeout);
EXPECT_EQ(base::Seconds(kExpectedIdleTimeout),
ui_model->lock_screen_inactivity_timeout());
pref_service->SetInteger(
ambient::prefs::kAmbientModeManagedScreensaverImageDisplayIntervalSeconds,
kExpectedPhotoRefreshInterval);
EXPECT_EQ(base::Seconds(kExpectedPhotoRefreshInterval),
ui_model->photo_refresh_interval());
}
TEST_F(AmbientControllerTest, RendersCorrectViewForVideo) {
SetAmbientUiSettings(
AmbientUiSettings(AmbientTheme::kVideo, AmbientVideo::kNewMexico));
LockScreen();
FastForwardToLockScreenTimeout();
FastForwardTiny();
ASSERT_TRUE(GetContainerView());
const TestAshWebView* web_view = static_cast<const TestAshWebView*>(
GetContainerView()->GetViewByID(kAmbientVideoWebView));
ASSERT_TRUE(web_view);
EXPECT_TRUE(web_view->current_url().SchemeIsFile());
EXPECT_EQ(web_view->current_url().path(),
personalization_app::GetTimeOfDaySrcDir()
.Append(personalization_app::kAmbientVideoHtml)
.value());
std::string video_path_requested;
ASSERT_TRUE(net::GetValueForKeyInQuery(web_view->current_url(), "video_src",
&video_path_requested));
GURL video_src_url(video_path_requested);
EXPECT_TRUE(video_src_url.SchemeIsFile());
EXPECT_EQ(video_src_url.path(),
personalization_app::GetTimeOfDayVideosDir()
.Append(personalization_app::kTimeOfDayNewMexicoVideo)
.value());
UnlockScreen();
SetAmbientTheme(AmbientTheme::kSlideshow);
LockScreen();
FastForwardToLockScreenTimeout();
FastForwardTiny();
ASSERT_TRUE(GetContainerView());
EXPECT_TRUE(
GetContainerView()->GetViewByID(AmbientViewID::kAmbientPhotoView));
UnlockScreen();
SetAmbientUiSettings(
AmbientUiSettings(AmbientTheme::kVideo, AmbientVideo::kClouds));
LockScreen();
FastForwardToLockScreenTimeout();
FastForwardTiny();
ASSERT_TRUE(GetContainerView());
web_view = static_cast<const TestAshWebView*>(
GetContainerView()->GetViewByID(kAmbientVideoWebView));
ASSERT_TRUE(web_view);
EXPECT_TRUE(web_view->current_url().SchemeIsFile());
EXPECT_EQ(web_view->current_url().path(),
personalization_app::GetTimeOfDaySrcDir()
.Append(personalization_app::kAmbientVideoHtml)
.value());
ASSERT_TRUE(net::GetValueForKeyInQuery(web_view->current_url(), "video_src",
&video_path_requested));
video_src_url = GURL(video_path_requested);
EXPECT_TRUE(video_src_url.SchemeIsFile());
EXPECT_EQ(video_src_url.path(),
personalization_app::GetTimeOfDayVideosDir()
.Append(personalization_app::kTimeOfDayCloudsVideo)
.value());
}
} // namespace ash