// Copyright 2022 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/ash/floating_workspace/floating_workspace_service.h"
#include <memory>
#include "ash/constants/ash_features.h"
#include "ash/public/cpp/desk_template.h"
#include "ash/shell.h"
#include "ash/system/tray/system_tray_notifier.h"
#include "ash/test/ash_test_helper.h"
#include "ash/wm/desks/desk.h"
#include "ash/wm/desks/templates/saved_desk_metrics_util.h"
#include "base/files/scoped_temp_dir.h"
#include "base/memory/raw_ptr.h"
#include "base/ranges/algorithm.h"
#include "base/strings/utf_string_conversions.h"
#include "base/test/bind.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/task_environment.h"
#include "base/time/time.h"
#include "chrome/browser/ash/floating_workspace/floating_workspace_metrics_util.h"
#include "chrome/browser/ash/floating_workspace/floating_workspace_service_factory.h"
#include "chrome/browser/ash/floating_workspace/floating_workspace_util.h"
#include "chrome/browser/notifications/notification_display_service_tester.h"
#include "chrome/browser/prefs/browser_prefs.h"
#include "chrome/browser/profiles/profile_keyed_service_factory.h"
#include "chrome/browser/ui/ash/desks/desks_client.h"
#include "chrome/browser/ui/ash/multi_user/multi_user_util.h"
#include "chrome/browser/ui/ash/session/session_controller_client_impl.h"
#include "chrome/browser/ui/ash/session/test_session_controller.h"
#include "chrome/test/base/testing_browser_process.h"
#include "chrome/test/base/testing_profile.h"
#include "chrome/test/base/testing_profile_manager.h"
#include "chromeos/ash/components/login/session/session_termination_manager.h"
#include "chromeos/ash/components/network/network_handler.h"
#include "chromeos/ash/components/network/network_handler_test_helper.h"
#include "chromeos/ash/components/network/network_state_handler.h"
#include "chromeos/dbus/power/fake_power_manager_client.h"
#include "components/account_id/account_id.h"
#include "components/app_restore/app_launch_info.h"
#include "components/app_restore/full_restore_utils.h"
#include "components/app_restore/window_info.h"
#include "components/app_restore/window_properties.h"
#include "components/desks_storage/core/desk_test_util.h"
#include "components/desks_storage/core/fake_desk_sync_service.h"
#include "components/services/app_service/public/cpp/app_registry_cache_wrapper.h"
#include "components/session_manager/core/session_manager.h"
#include "components/sync/base/data_type.h"
#include "components/sync/service/sync_service.h"
#include "components/sync/test/test_sync_service.h"
#include "components/sync_device_info/device_info.h"
#include "components/sync_device_info/fake_device_info_sync_service.h"
#include "components/sync_preferences/testing_pref_service_syncable.h"
#include "components/sync_sessions/open_tabs_ui_delegate.h"
#include "components/sync_sessions/synced_session.h"
#include "components/user_manager/fake_user_manager.h"
#include "components/user_manager/scoped_user_manager.h"
#include "content/public/test/browser_task_environment.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/cros_system_api/dbus/shill/dbus-constants.h"
#include "ui/base/user_activity/user_activity_detector.h"
#include "ui/message_center/public/cpp/notification.h"
namespace ash::floating_workspace {
namespace {
constexpr char kLocalSessionName[] = "local_session";
constexpr char kRemoteSessionOneName[] = "remote_session_1";
constexpr char kRemoteSession2Name[] = "remote_session_2";
constexpr char kTestAccount[] = "[email protected]";
constexpr char kTestAccount2[] = "[email protected]";
const base::Time most_recent_time = base::Time::FromSecondsSinceUnixEpoch(15);
const base::Time more_recent_time = base::Time::FromSecondsSinceUnixEpoch(10);
const base::Time least_recent_time = base::Time::FromSecondsSinceUnixEpoch(5);
std::unique_ptr<sync_sessions::SyncedSession> CreateNewSession(
const std::string& session_name,
const base::Time& session_time) {
auto session = std::make_unique<sync_sessions::SyncedSession>();
session->SetSessionName(session_name);
session->SetModifiedTime(session_time);
return session;
}
// Creates an app_restore::RestoreData object with `num_windows.size()` apps,
// where the ith app has `num_windows[i]` windows. The windows
// activation index is its creation order.
std::unique_ptr<app_restore::RestoreData> CreateRestoreData(
std::vector<int> num_windows) {
auto restore_data = std::make_unique<app_restore::RestoreData>();
int32_t activation_index_counter = 0;
for (size_t i = 0; i < num_windows.size(); ++i) {
const std::string app_id = base::NumberToString(i);
for (int32_t window_id = 0; window_id < num_windows[i]; ++window_id) {
restore_data->AddAppLaunchInfo(
std::make_unique<app_restore::AppLaunchInfo>(app_id, window_id));
app_restore::WindowInfo window_info;
window_info.activation_index =
std::make_optional<int32_t>(activation_index_counter++);
restore_data->ModifyWindowInfo(app_id, window_id, window_info);
}
}
return restore_data;
}
std::unique_ptr<syncer::DeviceInfo> CreateFakeDeviceInfo(
const std::string& guid,
const std::string& name,
base::Time last_updated_timestamp) {
return std::make_unique<syncer::DeviceInfo>(
guid, name, "chrome_version", "user_agent",
sync_pb::SyncEnums::TYPE_UNSET, syncer::DeviceInfo::OsType::kUnknown,
syncer::DeviceInfo::FormFactor::kUnknown, "device_id",
"manufacturer_name", "model_name", "full_hardware_class",
last_updated_timestamp, base::Minutes(60),
/*send_tab_to_self_receiving_enabled=*/
false,
/*send_tab_to_self_receiving_type=*/
sync_pb::
SyncEnums_SendTabReceivingType_SEND_TAB_RECEIVING_TYPE_CHROME_OR_UNSPECIFIED,
/*sharing_info=*/std::nullopt, /*paask_info=*/std::nullopt, "token",
syncer::DataTypeSet(),
/*floating_workspace_last_signin_timestamp=*/last_updated_timestamp);
}
std::unique_ptr<ash::DeskTemplate> MakeTestFloatingWorkspaceDeskTemplate(
std::string name,
base::Time creation_time) {
std::unique_ptr<ash::DeskTemplate> desk_template =
std::make_unique<ash::DeskTemplate>(
base::Uuid::GenerateRandomV4(), ash::DeskTemplateSource::kUser, name,
creation_time, DeskTemplateType::kFloatingWorkspace);
std::unique_ptr<app_restore::RestoreData> restore_data =
CreateRestoreData(std::vector<int>(10, 1));
desk_template->set_desk_restore_data(std::move(restore_data));
return desk_template;
}
class MockDesksClient : public DesksClient {
public:
MockDesksClient() = default;
MOCK_METHOD((base::expected<std::vector<const ash::Desk*>, DeskActionError>),
GetAllDesks,
(),
(override));
MOCK_METHOD((std::optional<DesksClient::DeskActionError>),
RemoveDesk,
(const base::Uuid& desk_uuid, ash::DeskCloseType close_type),
(override));
MOCK_METHOD((base::Uuid), GetActiveDesk, (), (override));
void CaptureActiveDesk(CaptureActiveDeskAndSaveTemplateCallback callback,
ash::DeskTemplateType template_type) override {
std::move(callback).Run(std::nullopt, captured_desk_template_ != nullptr
? captured_desk_template_->Clone()
: nullptr);
}
void LaunchAppsFromTemplate(
std::unique_ptr<ash::DeskTemplate> desk_template) override {
restored_template_uuid_ = desk_template->uuid();
restored_desk_template_ = std::move(desk_template);
}
void LaunchDeskTemplate(
const base::Uuid& template_uuid,
LaunchDeskCallback callback,
const std::u16string& customized_desk_name = std::u16string()) override {
restored_template_uuid_ = template_uuid;
std::move(callback).Run(std::nullopt, base::Uuid::GenerateRandomV4());
}
DeskTemplate* restored_desk_template() {
return restored_desk_template_.get();
}
base::Uuid& restored_template_uuid() { return restored_template_uuid_; }
void SetCapturedDeskTemplate(
std::unique_ptr<const DeskTemplate> captured_template) {
captured_desk_template_ = std::move(captured_template);
}
private:
std::unique_ptr<const DeskTemplate> captured_desk_template_;
std::unique_ptr<DeskTemplate> restored_desk_template_;
base::Uuid restored_template_uuid_;
};
class MockOpenTabsUIDelegate : public sync_sessions::OpenTabsUIDelegate {
public:
MockOpenTabsUIDelegate() = default;
bool GetAllForeignSessions(
std::vector<raw_ptr<const sync_sessions::SyncedSession,
VectorExperimental>>* sessions) override {
*sessions = foreign_sessions_;
base::ranges::sort(*sessions, std::greater(),
[](const sync_sessions::SyncedSession* session) {
return session->GetModifiedTime();
});
return !sessions->empty();
}
bool GetLocalSession(
const sync_sessions::SyncedSession** local_session) override {
*local_session = local_session_;
return *local_session != nullptr;
}
void SetForeignSessionsForTesting(
std::vector<raw_ptr<const sync_sessions::SyncedSession,
VectorExperimental>> foreign_sessions) {
foreign_sessions_ = foreign_sessions;
}
void SetLocalSessionForTesting(sync_sessions::SyncedSession* local_session) {
local_session_ = local_session;
}
MOCK_METHOD3(GetForeignTab,
bool(const std::string& tag,
const SessionID tab_id,
const sessions::SessionTab** tab));
MOCK_METHOD1(DeleteForeignSession, void(const std::string& tag));
MOCK_METHOD1(
GetForeignSession,
std::vector<const sessions::SessionWindow*>(const std::string& tag));
MOCK_METHOD2(GetForeignSessionTabs,
bool(const std::string& tag,
std::vector<const sessions::SessionTab*>* tabs));
private:
std::vector<raw_ptr<const sync_sessions::SyncedSession, VectorExperimental>>
foreign_sessions_;
raw_ptr<sync_sessions::SyncedSession, DanglingUntriaged> local_session_ =
nullptr;
};
} // namespace
class TestFloatingWorkSpaceService : public FloatingWorkspaceService {
public:
explicit TestFloatingWorkSpaceService(
TestingProfile* profile,
raw_ptr<desks_storage::FakeDeskSyncService> fake_desk_sync_service,
raw_ptr<syncer::TestSyncService> mock_sync_service,
raw_ptr<syncer::FakeDeviceInfoSyncService> fake_device_info_sync_service,
floating_workspace_util::FloatingWorkspaceVersion version)
: FloatingWorkspaceService(profile, version) {
Init(mock_sync_service, fake_desk_sync_service,
fake_device_info_sync_service);
mock_open_tabs_ = std::make_unique<MockOpenTabsUIDelegate>();
}
void RestoreLocalSessionWindows() override {
mock_open_tabs_->GetLocalSession(&restored_session_.AsEphemeralRawAddr());
}
void RestoreForeignSessionWindows(
const sync_sessions::SyncedSession* session) override {
restored_session_ = session;
}
const sync_sessions::SyncedSession* GetRestoredSession() {
return restored_session_.get();
}
void SetLocalSessionForTesting(sync_sessions::SyncedSession* session) {
mock_open_tabs_->SetLocalSessionForTesting(session);
}
void SetForeignSessionForTesting(
std::vector<raw_ptr<const sync_sessions::SyncedSession,
VectorExperimental>> foreign_sessions) {
mock_open_tabs_->SetForeignSessionsForTesting(foreign_sessions);
}
private:
sync_sessions::OpenTabsUIDelegate* GetOpenTabsUIDelegate() override {
return mock_open_tabs_.get();
}
void LaunchFloatingWorkspaceTemplate(
const DeskTemplate* desk_template) override {
restored_floating_workspace_template_ = desk_template;
}
raw_ptr<const sync_sessions::SyncedSession> restored_session_ = nullptr;
raw_ptr<const DeskTemplate, DanglingUntriaged>
restored_floating_workspace_template_ = nullptr;
raw_ptr<DeskTemplate> uploaded_desk_template_ = nullptr;
std::unique_ptr<MockOpenTabsUIDelegate> mock_open_tabs_;
};
class FloatingWorkspaceServiceTest : public testing::Test {
public:
FloatingWorkspaceServiceTest() = default;
~FloatingWorkspaceServiceTest() override = default;
TestingProfile* profile() const { return profile_.get(); }
content::BrowserTaskEnvironment& task_environment() {
return task_environment_;
}
desks_storage::FakeDeskSyncService* fake_desk_sync_service() {
return fake_desk_sync_service_.get();
}
base::test::ScopedFeatureList& scoped_feature_list() {
return scoped_feature_list_;
}
NotificationDisplayServiceTester* display_service() {
return display_service_.get();
}
syncer::TestSyncService* test_sync_service() { return &test_sync_service_; }
ui::UserActivityDetector* user_activity_detector() {
return ui::UserActivityDetector::Get();
}
syncer::FakeDeviceInfoSyncService* fake_device_info_sync_service() {
return fake_device_info_sync_service_.get();
}
user_manager::FakeUserManager* fake_user_manager() const {
return fake_user_manager_.Get();
}
MockDesksClient* mock_desks_client() { return mock_desks_client_.get(); }
TestingProfileManager* profile_manager() { return profile_manager_.get(); }
NetworkHandlerTestHelper* network_handler_test_helper() {
return network_handler_test_helper_.get();
}
chromeos::FakePowerManagerClient* power_manager_client() {
return chromeos::FakePowerManagerClient::Get();
}
bool HasNotificationFor(const std::string& id) {
std::optional<message_center::Notification> notification =
display_service()->GetNotification(id);
return notification.has_value();
}
void AddTestNetworkDevice() {
network_handler_test_helper_->AddDefaultProfiles();
}
void CleanUpTestNetworkDevices() {
network_handler_test_helper_->ClearDevices();
network_handler_test_helper_->ClearServices();
network_handler_test_helper_->ClearProfiles();
}
apps::AppRegistryCache* cache() { return cache_.get(); }
AccountId& account_id() { return account_id_; }
AshTestHelper* ash_test_helper() { return &ash_test_helper_; }
// We want to hold off on populating the apps cache before each test is run
// because the list of initialization types do not get reset. To test that the
// service is actually waiting for the app types to initialize, we need to
// keep it empty before then. For all other tests, this needs to be called
// before we get the `kUpToDate` from the sync service.
void PopulateAppsCache() {
desks_storage::desk_test_util::PopulateFloatingWorkspaceAppRegistryCache(
account_id_, cache_.get());
task_environment_.RunUntilIdle();
}
void CreateFloatingWorkspaceServiceForTesting(TestingProfile* profile) {
FloatingWorkspaceServiceFactory::GetInstance()->SetTestingFactoryAndUse(
profile, base::BindRepeating([](content::BrowserContext* context)
-> std::unique_ptr<KeyedService> {
return std::make_unique<FloatingWorkspaceService>(
Profile::FromBrowserContext(context),
floating_workspace_util::FloatingWorkspaceVersion::
kFloatingWorkspaceV2Enabled);
}));
task_environment_.RunUntilIdle();
}
void SetUp() override {
chromeos::PowerManagerClient::InitializeFake();
ash::AshTestHelper::InitParams params;
ash_test_helper_.SetUp(std::move(params));
profile_manager_ = std::make_unique<TestingProfileManager>(
TestingBrowserProcess::GetGlobal());
ASSERT_TRUE(profile_manager_->SetUp());
base::ScopedTempDir temp_dir;
ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
fake_user_manager_.Reset(std::make_unique<user_manager::FakeUserManager>(
TestingBrowserProcess::GetGlobal()->local_state()));
account_id_ = AccountId::FromUserEmail(kTestAccount);
const std::string username_hash =
user_manager::FakeUserManager::GetFakeUsernameHash(account_id_);
fake_user_manager()->AddUser(account_id_);
fake_user_manager()->UserLoggedIn(account_id_, username_hash,
/*browser_restart=*/false,
/*is_child=*/false);
CoreAccountInfo account_info;
account_info.email = kTestAccount;
account_info.gaia = "gaia";
account_info.account_id = CoreAccountId::FromGaiaId(account_info.gaia);
test_sync_service_.SetSignedIn(signin::ConsentLevel::kSync, account_info);
auto prefs =
std::make_unique<sync_preferences::TestingPrefServiceSyncable>();
RegisterUserProfilePrefs(prefs->registry());
auto* prefs_ptr = prefs.get();
profile_ = profile_manager_->CreateTestingProfile(
kTestAccount, std::move(prefs), std::u16string(), /*avatar_id=*/0,
TestingProfile::TestingFactories());
fake_user_manager()->OnUserProfileCreated(account_id_, prefs_ptr);
fake_desk_sync_service_ =
std::make_unique<desks_storage::FakeDeskSyncService>(
/*skip_engine_connection=*/true);
display_service_ =
std::make_unique<NotificationDisplayServiceTester>(profile_.get());
network_handler_test_helper_ = std::make_unique<NetworkHandlerTestHelper>();
fake_device_info_sync_service_ =
std::make_unique<syncer::FakeDeviceInfoSyncService>(
/*skip_engine_connection=*/true);
AddTestNetworkDevice();
test_sync_service()->SetDownloadStatusFor(
{syncer::DataType::DEVICE_INFO},
syncer::SyncService::DataTypeDownloadStatus::kWaitingForUpdates);
user_activity_detector()->set_last_activity_time_for_test(
base::TimeTicks::Now());
cache_ = std::make_unique<apps::AppRegistryCache>();
apps::AppRegistryCacheWrapper::Get().AddAppRegistryCache(account_id_,
cache_.get());
mock_desks_client_ = std::make_unique<MockDesksClient>();
}
void TearDown() override {
auto* floating_workspace_service =
FloatingWorkspaceService::GetForProfile(profile());
if (floating_workspace_service) {
floating_workspace_service->ShutDownServicesAndObservers();
}
fake_user_manager()->OnUserProfileWillBeDestroyed(account_id_);
profile_ = nullptr;
profile_manager_ = nullptr;
mock_desks_client_ = nullptr;
ash_test_helper_.TearDown();
chromeos::PowerManagerClient::Shutdown();
}
private:
content::BrowserTaskEnvironment task_environment_{
base::test::TaskEnvironment::TimeSource::MOCK_TIME};
syncer::TestSyncService test_sync_service_;
std::unique_ptr<desks_storage::FakeDeskSyncService> fake_desk_sync_service_;
std::unique_ptr<NotificationDisplayServiceTester> display_service_;
std::unique_ptr<syncer::FakeDeviceInfoSyncService>
fake_device_info_sync_service_;
base::test::ScopedFeatureList scoped_feature_list_;
std::unique_ptr<NetworkHandlerTestHelper> network_handler_test_helper_;
std::unique_ptr<apps::AppRegistryCache> cache_;
AccountId account_id_;
AshTestHelper ash_test_helper_;
std::unique_ptr<TestingProfileManager> profile_manager_;
std::unique_ptr<MockDesksClient> mock_desks_client_;
user_manager::TypedScopedUserManager<user_manager::FakeUserManager>
fake_user_manager_;
ash::SessionTerminationManager session_termination_manager_;
raw_ptr<TestingProfile> profile_ = nullptr;
};
class FloatingWorkspaceServiceV1Test : public FloatingWorkspaceServiceTest {
protected:
FloatingWorkspaceServiceV1Test() = default;
~FloatingWorkspaceServiceV1Test() override = default;
void SetUp() override {
scoped_feature_list().InitWithFeatures({features::kFloatingWorkspace}, {});
FloatingWorkspaceServiceTest::SetUp();
}
void TearDown() override {
FloatingWorkspaceServiceTest::TearDown();
scoped_feature_list().Reset();
}
};
class FloatingWorkspaceServiceV2Test : public FloatingWorkspaceServiceTest {
protected:
FloatingWorkspaceServiceV2Test() = default;
~FloatingWorkspaceServiceV2Test() override = default;
void SetUp() override {
scoped_feature_list().InitWithFeatures(
{features::kFloatingWorkspaceV2, features::kDeskTemplateSync}, {});
FloatingWorkspaceServiceTest::SetUp();
}
void TearDown() override {
FloatingWorkspaceServiceTest::TearDown();
scoped_feature_list().Reset();
}
};
TEST_F(FloatingWorkspaceServiceV1Test, RestoreRemoteSession) {
PopulateAppsCache();
std::unique_ptr<sync_sessions::SyncedSession> local_session =
CreateNewSession(kLocalSessionName, more_recent_time);
std::vector<raw_ptr<const sync_sessions::SyncedSession, VectorExperimental>>
foreign_sessions;
// This remote session has most recent timestamp and should be restored.
const std::unique_ptr<sync_sessions::SyncedSession>
most_recent_remote_session =
CreateNewSession(kRemoteSessionOneName, most_recent_time);
const std::unique_ptr<sync_sessions::SyncedSession>
less_recent_remote_session =
CreateNewSession(kRemoteSession2Name, least_recent_time);
foreign_sessions.push_back(less_recent_remote_session.get());
foreign_sessions.push_back(most_recent_remote_session.get());
TestFloatingWorkSpaceService test_floating_workspace_service(
profile(), /*fake_desk_sync_service=*/nullptr,
/*mock_sync_service=*/nullptr,
/*fake_device_info_sync_service*/ nullptr,
floating_workspace_util::FloatingWorkspaceVersion::
kFloatingWorkspaceV1Enabled);
test_floating_workspace_service.SetLocalSessionForTesting(
local_session.get());
test_floating_workspace_service.SetForeignSessionForTesting(foreign_sessions);
test_floating_workspace_service
.RestoreBrowserWindowsFromMostRecentlyUsedDevice();
task_environment().FastForwardBy(
ash::features::kFloatingWorkspaceMaxTimeAvailableForRestoreAfterLogin
.Get() +
base::Seconds(1));
EXPECT_TRUE(test_floating_workspace_service.GetRestoredSession());
EXPECT_EQ(
kRemoteSessionOneName,
test_floating_workspace_service.GetRestoredSession()->GetSessionName());
}
TEST_F(FloatingWorkspaceServiceV1Test, RestoreLocalSession) {
PopulateAppsCache();
// Local session has most recent timestamp and should be restored.
std::unique_ptr<sync_sessions::SyncedSession> local_session =
CreateNewSession(kLocalSessionName, most_recent_time);
std::vector<raw_ptr<const sync_sessions::SyncedSession, VectorExperimental>>
foreign_sessions;
const std::unique_ptr<sync_sessions::SyncedSession>
most_recent_remote_session =
CreateNewSession(kRemoteSessionOneName, more_recent_time);
const std::unique_ptr<sync_sessions::SyncedSession>
less_recent_remote_session =
CreateNewSession(kRemoteSession2Name, least_recent_time);
foreign_sessions.push_back(less_recent_remote_session.get());
foreign_sessions.push_back(most_recent_remote_session.get());
TestFloatingWorkSpaceService test_floating_workspace_service(
profile(), /*fake_desk_sync_service=*/nullptr,
/*mock_sync_service=*/nullptr,
/*fake_device_info_sync_service*/ nullptr,
floating_workspace_util::FloatingWorkspaceVersion::
kFloatingWorkspaceV1Enabled);
test_floating_workspace_service.SetLocalSessionForTesting(
local_session.get());
test_floating_workspace_service.SetForeignSessionForTesting(foreign_sessions);
test_floating_workspace_service
.RestoreBrowserWindowsFromMostRecentlyUsedDevice();
task_environment().FastForwardBy(
ash::features::kFloatingWorkspaceMaxTimeAvailableForRestoreAfterLogin
.Get() +
base::Seconds(1));
EXPECT_TRUE(test_floating_workspace_service.GetRestoredSession());
EXPECT_EQ(
kLocalSessionName,
test_floating_workspace_service.GetRestoredSession()->GetSessionName());
}
TEST_F(FloatingWorkspaceServiceV1Test, RestoreRemoteSessionAfterUpdated) {
PopulateAppsCache();
// Local session has most recent timestamp and should be restored.
std::unique_ptr<sync_sessions::SyncedSession> local_session =
CreateNewSession(kLocalSessionName, most_recent_time);
std::vector<raw_ptr<const sync_sessions::SyncedSession, VectorExperimental>>
foreign_sessions;
const std::unique_ptr<sync_sessions::SyncedSession>
most_recent_remote_session =
CreateNewSession(kRemoteSessionOneName, more_recent_time);
const std::unique_ptr<sync_sessions::SyncedSession>
less_recent_remote_session =
CreateNewSession(kRemoteSession2Name, least_recent_time);
foreign_sessions.push_back(less_recent_remote_session.get());
foreign_sessions.push_back(most_recent_remote_session.get());
TestFloatingWorkSpaceService test_floating_workspace_service(
profile(), /*fake_desk_sync_service=*/nullptr,
/*mock_sync_service=*/nullptr,
/*fake_device_info_sync_service*/ nullptr,
floating_workspace_util::FloatingWorkspaceVersion::
kFloatingWorkspaceV1Enabled);
test_floating_workspace_service.SetLocalSessionForTesting(
local_session.get());
test_floating_workspace_service.SetForeignSessionForTesting(foreign_sessions);
test_floating_workspace_service
.RestoreBrowserWindowsFromMostRecentlyUsedDevice();
// Simulate remote session update arrives 1 seconds after service
// initialization.
base::TimeDelta remote_session_update_arrival_time = base::Seconds(1);
task_environment().FastForwardBy(remote_session_update_arrival_time);
// Remote session got updated during the 3 second delay of dispatching task
// and updated remote session is most recent.
base::Time remote_session_updated_time = most_recent_time + base::Seconds(5);
// Now previously less recent remote session becomes most recent
// and should be restored.
less_recent_remote_session->SetModifiedTime(remote_session_updated_time);
task_environment().FastForwardBy(
ash::features::kFloatingWorkspaceMaxTimeAvailableForRestoreAfterLogin
.Get() -
remote_session_update_arrival_time);
EXPECT_TRUE(test_floating_workspace_service.GetRestoredSession());
EXPECT_EQ(
less_recent_remote_session->GetSessionName(),
test_floating_workspace_service.GetRestoredSession()->GetSessionName());
}
TEST_F(FloatingWorkspaceServiceV1Test, NoLocalSession) {
PopulateAppsCache();
std::vector<raw_ptr<const sync_sessions::SyncedSession, VectorExperimental>>
foreign_sessions;
const std::unique_ptr<sync_sessions::SyncedSession>
most_recent_remote_session =
CreateNewSession(kRemoteSessionOneName, more_recent_time);
const std::unique_ptr<sync_sessions::SyncedSession>
less_recent_remote_session =
CreateNewSession(kRemoteSession2Name, least_recent_time);
foreign_sessions.push_back(less_recent_remote_session.get());
foreign_sessions.push_back(most_recent_remote_session.get());
TestFloatingWorkSpaceService test_floating_workspace_service(
profile(), /*fake_desk_sync_service=*/nullptr,
/*mock_sync_service=*/nullptr,
/*fake_device_info_sync_service*/ nullptr,
floating_workspace_util::FloatingWorkspaceVersion::
kFloatingWorkspaceV1Enabled);
test_floating_workspace_service.SetForeignSessionForTesting(foreign_sessions);
test_floating_workspace_service
.RestoreBrowserWindowsFromMostRecentlyUsedDevice();
// Wait for kFloatingWorkspaceMaxTimeAvailableForRestoreAfterLogin seconds.
task_environment().FastForwardBy(
ash::features::kFloatingWorkspaceMaxTimeAvailableForRestoreAfterLogin
.Get());
EXPECT_TRUE(test_floating_workspace_service.GetRestoredSession());
EXPECT_EQ(
most_recent_remote_session->GetSessionName(),
test_floating_workspace_service.GetRestoredSession()->GetSessionName());
}
TEST_F(FloatingWorkspaceServiceV1Test, NoRemoteSession) {
PopulateAppsCache();
std::unique_ptr<sync_sessions::SyncedSession> local_session =
CreateNewSession(kLocalSessionName, least_recent_time);
TestFloatingWorkSpaceService test_floating_workspace_service(
profile(), /*fake_desk_sync_service=*/nullptr,
/*mock_sync_service=*/nullptr, /*fake_device_info_sync_service*/ nullptr,
floating_workspace_util::FloatingWorkspaceVersion::
kFloatingWorkspaceV1Enabled);
test_floating_workspace_service.SetLocalSessionForTesting(
local_session.get());
test_floating_workspace_service
.RestoreBrowserWindowsFromMostRecentlyUsedDevice();
// Wait for kFloatingWorkspaceMaxTimeAvailableForRestoreAfterLogin seconds.
task_environment().FastForwardBy(
ash::features::kFloatingWorkspaceMaxTimeAvailableForRestoreAfterLogin
.Get());
EXPECT_TRUE(test_floating_workspace_service.GetRestoredSession());
EXPECT_EQ(
kLocalSessionName,
test_floating_workspace_service.GetRestoredSession()->GetSessionName());
}
TEST_F(FloatingWorkspaceServiceV1Test, NoSession) {
PopulateAppsCache();
TestFloatingWorkSpaceService test_floating_workspace_service(
profile(), /*fake_desk_sync_service=*/nullptr,
/*mock_sync_service=*/nullptr, /*fake_device_info_sync_service*/ nullptr,
floating_workspace_util::FloatingWorkspaceVersion::
kFloatingWorkspaceV1Enabled);
test_floating_workspace_service
.RestoreBrowserWindowsFromMostRecentlyUsedDevice();
// Wait for kFloatingWorkspaceMaxTimeAvailableForRestoreAfterLogin seconds.
task_environment().FastForwardBy(
ash::features::kFloatingWorkspaceMaxTimeAvailableForRestoreAfterLogin
.Get());
EXPECT_FALSE(test_floating_workspace_service.GetRestoredSession());
}
TEST_F(FloatingWorkspaceServiceV2Test, RestoreFloatingWorkspaceTemplate) {
PopulateAppsCache();
const std::string template_name = "floating_workspace_template";
base::RunLoop loop;
fake_desk_sync_service()->GetDeskModel()->AddOrUpdateEntry(
MakeTestFloatingWorkspaceDeskTemplate(template_name, base::Time::Now()),
base::BindLambdaForTesting(
[&](desks_storage::DeskModel::AddOrUpdateEntryStatus status,
std::unique_ptr<ash::DeskTemplate> new_entry) {
EXPECT_EQ(desks_storage::DeskModel::AddOrUpdateEntryStatus::kOk,
status);
loop.Quit();
}));
loop.Run();
CreateFloatingWorkspaceServiceForTesting(profile());
auto* floating_workspace_service =
FloatingWorkspaceService::GetForProfile(profile());
floating_workspace_service->Init(test_sync_service(),
fake_desk_sync_service(),
fake_device_info_sync_service());
test_sync_service()->SetDownloadStatusFor(
{syncer::DataType::WORKSPACE_DESK},
syncer::SyncService::DataTypeDownloadStatus::kUpToDate);
test_sync_service()->FireStateChanged();
ASSERT_TRUE(mock_desks_client()->restored_desk_template());
EXPECT_EQ(mock_desks_client()->restored_desk_template()->template_name(),
base::UTF8ToUTF16(template_name));
}
TEST_F(FloatingWorkspaceServiceV2Test, NoNetworkForFloatingWorkspaceTemplate) {
PopulateAppsCache();
CleanUpTestNetworkDevices();
CreateFloatingWorkspaceServiceForTesting(profile());
auto* floating_workspace_service =
FloatingWorkspaceService::GetForProfile(profile());
floating_workspace_service->Init(test_sync_service(),
fake_desk_sync_service(),
fake_device_info_sync_service());
task_environment().RunUntilIdle();
EXPECT_TRUE(HasNotificationFor(kNotificationForNoNetworkConnection));
}
TEST_F(FloatingWorkspaceServiceV2Test,
NoNetworkForFloatingWorkspaceTemplateAfterLongDelay) {
PopulateAppsCache();
CreateFloatingWorkspaceServiceForTesting(profile());
auto* floating_workspace_service =
FloatingWorkspaceService::GetForProfile(profile());
floating_workspace_service->Init(test_sync_service(),
fake_desk_sync_service(),
fake_device_info_sync_service());
task_environment().RunUntilIdle();
EXPECT_FALSE(HasNotificationFor(kNotificationForNoNetworkConnection));
task_environment().FastForwardBy(
ash::features::kFloatingWorkspaceV2MaxTimeAvailableForRestoreAfterLogin
.Get() -
base::Milliseconds(1));
CleanUpTestNetworkDevices();
task_environment().RunUntilIdle();
EXPECT_TRUE(HasNotificationFor(kNotificationForNoNetworkConnection));
}
TEST_F(
FloatingWorkspaceServiceV2Test,
NoNetworkForFloatingWorkspaceTemplateNotificationGoneAfterNetworkIsConnected) {
PopulateAppsCache();
CleanUpTestNetworkDevices();
CreateFloatingWorkspaceServiceForTesting(profile());
auto* floating_workspace_service =
FloatingWorkspaceService::GetForProfile(profile());
floating_workspace_service->Init(test_sync_service(),
fake_desk_sync_service(),
fake_device_info_sync_service());
task_environment().RunUntilIdle();
EXPECT_TRUE(HasNotificationFor(kNotificationForNoNetworkConnection));
AddTestNetworkDevice();
network_handler_test_helper()->ResetDevicesAndServices();
network_handler_test_helper()->ConfigureService(
R"({"GUID": "wifi1_guid", "Type": "wifi", "State": "online",
"Strength": 50, "AutoConnect": true, "WiFi.HiddenSSID":
false})");
task_environment().RunUntilIdle();
floating_workspace_service->DefaultNetworkChanged(
NetworkHandler::Get()->network_state_handler()->DefaultNetwork());
EXPECT_FALSE(HasNotificationFor(kNotificationForNoNetworkConnection));
}
TEST_F(FloatingWorkspaceServiceV2Test,
PreventNetworkIssueNotifFromFiringAfterRestoreAttemptOrRestoreHappened) {
PopulateAppsCache();
const std::string template_name = "floating_workspace_template";
base::RunLoop loop;
fake_desk_sync_service()->GetDeskModel()->AddOrUpdateEntry(
MakeTestFloatingWorkspaceDeskTemplate(template_name, base::Time::Now()),
base::BindLambdaForTesting(
[&](desks_storage::DeskModel::AddOrUpdateEntryStatus status,
std::unique_ptr<ash::DeskTemplate> new_entry) {
EXPECT_EQ(desks_storage::DeskModel::AddOrUpdateEntryStatus::kOk,
status);
loop.Quit();
}));
loop.Run();
CreateFloatingWorkspaceServiceForTesting(profile());
auto* floating_workspace_service =
FloatingWorkspaceService::GetForProfile(profile());
floating_workspace_service->Init(test_sync_service(),
fake_desk_sync_service(),
fake_device_info_sync_service());
test_sync_service()->SetDownloadStatusFor(
{syncer::DataType::WORKSPACE_DESK},
syncer::SyncService::DataTypeDownloadStatus::kUpToDate);
test_sync_service()->FireStateChanged();
ASSERT_TRUE(mock_desks_client()->restored_desk_template());
EXPECT_EQ(mock_desks_client()->restored_desk_template()->template_name(),
base::UTF8ToUTF16(template_name));
// Disconnect from internet. Make sure no notification is sent since restore
// happened already.
CleanUpTestNetworkDevices();
task_environment().RunUntilIdle();
EXPECT_FALSE(HasNotificationFor(kNotificationForNoNetworkConnection));
// Sanity check. Add network back and make sure notification is still gone.
AddTestNetworkDevice();
network_handler_test_helper()->ResetDevicesAndServices();
network_handler_test_helper()->ConfigureService(
R"({"GUID": "wifi1_guid", "Type": "wifi", "State": "online",
"Strength": 50, "AutoConnect": true, "WiFi.HiddenSSID":
false})");
floating_workspace_service->DefaultNetworkChanged(
NetworkHandler::Get()->network_state_handler()->DefaultNetwork());
EXPECT_FALSE(HasNotificationFor(kNotificationForNoNetworkConnection));
}
TEST_F(FloatingWorkspaceServiceV2Test,
NoNetworkNotificationLogicWhenSyncIsInactiveAndOnceSyncIsActiveAgain) {
PopulateAppsCache();
CreateFloatingWorkspaceServiceForTesting(profile());
auto* floating_workspace_service =
FloatingWorkspaceService::GetForProfile(profile());
test_sync_service()->SetAllowedByEnterprisePolicy(false);
ASSERT_FALSE(test_sync_service()->IsSyncFeatureEnabled());
floating_workspace_service->Init(test_sync_service(),
fake_desk_sync_service(),
fake_device_info_sync_service());
test_sync_service()->FireStateChanged();
EXPECT_TRUE(HasNotificationFor(kNotificationForNoNetworkConnection));
test_sync_service()->SetAllowedByEnterprisePolicy(true);
ASSERT_TRUE(test_sync_service()->IsSyncFeatureEnabled());
test_sync_service()->FireStateChanged();
EXPECT_FALSE(HasNotificationFor(kNotificationForNoNetworkConnection));
}
TEST_F(FloatingWorkspaceServiceV2Test,
FloatingWorkspaceTemplateRestoreAfterTimeOut) {
PopulateAppsCache();
const std::string template_name = "floating_workspace_template";
base::RunLoop loop;
fake_desk_sync_service()->GetDeskModel()->AddOrUpdateEntry(
MakeTestFloatingWorkspaceDeskTemplate(template_name, base::Time::Now()),
base::BindLambdaForTesting(
[&](desks_storage::DeskModel::AddOrUpdateEntryStatus status,
std::unique_ptr<ash::DeskTemplate> new_entry) {
EXPECT_EQ(desks_storage::DeskModel::AddOrUpdateEntryStatus::kOk,
status);
loop.Quit();
}));
loop.Run();
CreateFloatingWorkspaceServiceForTesting(profile());
auto* floating_workspace_service =
FloatingWorkspaceService::GetForProfile(profile());
floating_workspace_service->Init(test_sync_service(),
fake_desk_sync_service(),
fake_device_info_sync_service());
task_environment().FastForwardBy(
ash::features::kFloatingWorkspaceV2MaxTimeAvailableForRestoreAfterLogin
.Get() +
base::Seconds(1));
EXPECT_TRUE(HasNotificationFor(kNotificationForSyncErrorOrTimeOut));
test_sync_service()->SetDownloadStatusFor(
{syncer::DataType::WORKSPACE_DESK},
syncer::SyncService::DataTypeDownloadStatus::kUpToDate);
test_sync_service()->FireStateChanged();
EXPECT_FALSE(mock_desks_client()->restored_desk_template());
EXPECT_TRUE(HasNotificationFor(kNotificationForRestoreAfterError));
// User clicks restore button on the notification.
display_service()->SimulateClick(
NotificationHandler::Type::TRANSIENT, kNotificationForRestoreAfterError,
static_cast<int>(RestoreFromErrorNotificationButtonIndex::kRestore),
std::nullopt);
EXPECT_TRUE(mock_desks_client()->restored_desk_template());
EXPECT_EQ(mock_desks_client()->restored_desk_template()->template_name(),
base::UTF8ToUTF16(template_name));
}
TEST_F(FloatingWorkspaceServiceV2Test,
FloatingWorkspaceTemplateDiscardAfterTimeOut) {
PopulateAppsCache();
const std::string template_name = "floating_workspace_template";
base::RunLoop loop;
fake_desk_sync_service()->GetDeskModel()->AddOrUpdateEntry(
MakeTestFloatingWorkspaceDeskTemplate(template_name, base::Time::Now()),
base::BindLambdaForTesting(
[&](desks_storage::DeskModel::AddOrUpdateEntryStatus status,
std::unique_ptr<ash::DeskTemplate> new_entry) {
EXPECT_EQ(desks_storage::DeskModel::AddOrUpdateEntryStatus::kOk,
status);
loop.Quit();
}));
loop.Run();
CreateFloatingWorkspaceServiceForTesting(profile());
auto* floating_workspace_service =
FloatingWorkspaceService::GetForProfile(profile());
floating_workspace_service->Init(test_sync_service(),
fake_desk_sync_service(),
fake_device_info_sync_service());
task_environment().FastForwardBy(
ash::features::kFloatingWorkspaceV2MaxTimeAvailableForRestoreAfterLogin
.Get() +
base::Seconds(1));
EXPECT_TRUE(HasNotificationFor(kNotificationForSyncErrorOrTimeOut));
// Download completes after timeout.
test_sync_service()->SetDownloadStatusFor(
{syncer::DataType::WORKSPACE_DESK},
syncer::SyncService::DataTypeDownloadStatus::kUpToDate);
test_sync_service()->FireStateChanged();
EXPECT_FALSE(mock_desks_client()->restored_desk_template());
EXPECT_TRUE(HasNotificationFor(kNotificationForRestoreAfterError));
// User clicks restore button on the notification.
display_service()->SimulateClick(
NotificationHandler::Type::TRANSIENT, kNotificationForRestoreAfterError,
static_cast<int>(RestoreFromErrorNotificationButtonIndex::kCancel),
std::nullopt);
EXPECT_FALSE(mock_desks_client()->restored_desk_template());
}
TEST_F(FloatingWorkspaceServiceV2Test, CanRecordTemplateLoadMetric) {
PopulateAppsCache();
const std::string template_name = "floating_workspace_template";
base::RunLoop loop;
fake_desk_sync_service()->GetDeskModel()->AddOrUpdateEntry(
MakeTestFloatingWorkspaceDeskTemplate(template_name, base::Time::Now()),
base::BindLambdaForTesting(
[&](desks_storage::DeskModel::AddOrUpdateEntryStatus status,
std::unique_ptr<ash::DeskTemplate> new_entry) {
EXPECT_EQ(desks_storage::DeskModel::AddOrUpdateEntryStatus::kOk,
status);
loop.Quit();
}));
loop.Run();
base::HistogramTester histogram_tester;
CreateFloatingWorkspaceServiceForTesting(profile());
auto* floating_workspace_service =
FloatingWorkspaceService::GetForProfile(profile());
floating_workspace_service->Init(test_sync_service(),
fake_desk_sync_service(),
fake_device_info_sync_service());
test_sync_service()->SetDownloadStatusFor(
{syncer::DataType::WORKSPACE_DESK},
syncer::SyncService::DataTypeDownloadStatus::kUpToDate);
test_sync_service()->FireStateChanged();
EXPECT_TRUE(mock_desks_client()->restored_desk_template());
EXPECT_EQ(mock_desks_client()->restored_desk_template()->template_name(),
base::UTF8ToUTF16(template_name));
histogram_tester.ExpectTotalCount(
floating_workspace_metrics_util::kFloatingWorkspaceV2TemplateLoadTime,
1u);
}
TEST_F(FloatingWorkspaceServiceV2Test, CanRecordTemplateLaunchTimeout) {
PopulateAppsCache();
base::HistogramTester histogram_tester;
const std::string template_name = "floating_workspace_template";
base::RunLoop loop;
fake_desk_sync_service()->GetDeskModel()->AddOrUpdateEntry(
MakeTestFloatingWorkspaceDeskTemplate(template_name, base::Time::Now()),
base::BindLambdaForTesting(
[&](desks_storage::DeskModel::AddOrUpdateEntryStatus status,
std::unique_ptr<ash::DeskTemplate> new_entry) {
EXPECT_EQ(desks_storage::DeskModel::AddOrUpdateEntryStatus::kOk,
status);
loop.Quit();
}));
loop.Run();
CreateFloatingWorkspaceServiceForTesting(profile());
auto* floating_workspace_service =
FloatingWorkspaceService::GetForProfile(profile());
floating_workspace_service->Init(test_sync_service(),
fake_desk_sync_service(),
fake_device_info_sync_service());
task_environment().FastForwardBy(
ash::features::kFloatingWorkspaceV2MaxTimeAvailableForRestoreAfterLogin
.Get() +
base::Seconds(1));
EXPECT_FALSE(mock_desks_client()->restored_desk_template());
histogram_tester.ExpectTotalCount(
floating_workspace_metrics_util::
kFloatingWorkspaceV2TemplateLaunchTimedOut,
1u);
histogram_tester.ExpectBucketCount(
floating_workspace_metrics_util::
kFloatingWorkspaceV2TemplateLaunchTimedOut,
static_cast<int>(floating_workspace_metrics_util::
LaunchTemplateTimeoutType::kPassedWaitPeriod),
1u);
}
TEST_F(FloatingWorkspaceServiceV2Test, CaptureFloatingWorkspaceTemplate) {
PopulateAppsCache();
CreateFloatingWorkspaceServiceForTesting(profile());
auto* floating_workspace_service =
FloatingWorkspaceService::GetForProfile(profile());
floating_workspace_service->Init(test_sync_service(),
fake_desk_sync_service(),
fake_device_info_sync_service());
const std::string template_name = "floating_workspace_captured_template";
const base::Time creation_time = base::Time::Now();
std::unique_ptr<DeskTemplate> floating_workspace_template =
MakeTestFloatingWorkspaceDeskTemplate(template_name, creation_time);
mock_desks_client()->SetCapturedDeskTemplate(
std::move(floating_workspace_template));
test_sync_service()->SetDownloadStatusFor(
{syncer::DataType::WORKSPACE_DESK},
syncer::SyncService::DataTypeDownloadStatus::kUpToDate);
test_sync_service()->FireStateChanged();
task_environment().FastForwardBy(
ash::features::kFloatingWorkspaceV2PeriodicJobIntervalInSeconds.Get() +
base::Seconds(1));
user_activity_detector()->set_last_activity_time_for_test(
base::TimeTicks::Now());
ASSERT_TRUE(floating_workspace_service->GetLatestFloatingWorkspaceTemplate());
EXPECT_EQ(floating_workspace_service->GetLatestFloatingWorkspaceTemplate()
->created_time(),
creation_time);
}
TEST_F(FloatingWorkspaceServiceV2Test, CaptureSameFloatingWorkspaceTemplate) {
// Upload should be skipped if two captured templates are the same.
PopulateAppsCache();
CreateFloatingWorkspaceServiceForTesting(profile());
auto* floating_workspace_service =
FloatingWorkspaceService::GetForProfile(profile());
floating_workspace_service->Init(test_sync_service(),
fake_desk_sync_service(),
fake_device_info_sync_service());
test_sync_service()->SetDownloadStatusFor(
{syncer::DataType::WORKSPACE_DESK},
syncer::SyncService::DataTypeDownloadStatus::kUpToDate);
test_sync_service()->FireStateChanged();
const std::string template_name = "floating_workspace_captured_template";
const base::Time first_captured_template_creation_time = base::Time::Now();
std::unique_ptr<DeskTemplate> first_captured_floating_workspace_template =
MakeTestFloatingWorkspaceDeskTemplate(
template_name, first_captured_template_creation_time);
mock_desks_client()->SetCapturedDeskTemplate(
std::move(first_captured_floating_workspace_template));
// Trigger the first capture task.
task_environment().FastForwardBy(
ash::features::kFloatingWorkspaceV2PeriodicJobIntervalInSeconds.Get() +
base::Seconds(1));
user_activity_detector()->set_last_activity_time_for_test(
base::TimeTicks::Now());
const base::Time second_captured_template_creation_time = base::Time::Now();
std::unique_ptr<DeskTemplate> second_captured_floating_workspace_template =
MakeTestFloatingWorkspaceDeskTemplate(
template_name, second_captured_template_creation_time);
// Set the 2nd template to be captured.
mock_desks_client()->SetCapturedDeskTemplate(
std::move(second_captured_floating_workspace_template));
// Fast forward by capture interval capture a second time.
task_environment().FastForwardBy(
ash::features::kFloatingWorkspaceV2PeriodicJobIntervalInSeconds.Get() +
base::Seconds(1));
EXPECT_TRUE(floating_workspace_service->GetLatestFloatingWorkspaceTemplate());
// Second captured template is the same as first, template should not be
// updated, creation time is first template's creation time.
EXPECT_EQ(floating_workspace_service->GetLatestFloatingWorkspaceTemplate()
->created_time(),
first_captured_template_creation_time);
}
TEST_F(FloatingWorkspaceServiceV2Test,
CaptureDifferentFloatingWorkspaceTemplate) {
// Upload should be executed if two captured templates are the different.
PopulateAppsCache();
CreateFloatingWorkspaceServiceForTesting(profile());
auto* floating_workspace_service =
FloatingWorkspaceService::GetForProfile(profile());
floating_workspace_service->Init(test_sync_service(),
fake_desk_sync_service(),
fake_device_info_sync_service());
test_sync_service()->SetDownloadStatusFor(
{syncer::DataType::WORKSPACE_DESK},
syncer::SyncService::DataTypeDownloadStatus::kUpToDate);
test_sync_service()->FireStateChanged();
const std::string template_name = "floating_workspace_captured_template";
const base::Time first_captured_template_creation_time = base::Time::Now();
std::unique_ptr<DeskTemplate> first_captured_floating_workspace_template =
MakeTestFloatingWorkspaceDeskTemplate(
template_name, first_captured_template_creation_time);
mock_desks_client()->SetCapturedDeskTemplate(
std::move(first_captured_floating_workspace_template));
// Trigger the first capture task.
task_environment().FastForwardBy(
ash::features::kFloatingWorkspaceV2PeriodicJobIntervalInSeconds.Get() +
base::Seconds(1));
user_activity_detector()->set_last_activity_time_for_test(
base::TimeTicks::Now());
const base::Time second_captured_template_creation_time = base::Time::Now();
std::unique_ptr<DeskTemplate> second_captured_floating_workspace_template =
MakeTestFloatingWorkspaceDeskTemplate(
template_name, second_captured_template_creation_time);
// Create new restore data different than 1st captured one.
std::unique_ptr<app_restore::RestoreData> restore_data =
CreateRestoreData(std::vector<int>(11, 1));
second_captured_floating_workspace_template->set_desk_restore_data(
std::move(restore_data));
// Set the 2nd template to be captured.
mock_desks_client()->SetCapturedDeskTemplate(
std::move(second_captured_floating_workspace_template));
// Fast forward by capture interval capture a second time.
task_environment().FastForwardBy(
ash::features::kFloatingWorkspaceV2PeriodicJobIntervalInSeconds.Get() +
base::Seconds(1));
EXPECT_TRUE(floating_workspace_service->GetLatestFloatingWorkspaceTemplate());
// Second captured template has different restore data than first, template
// should be updated, replacing the first one.
EXPECT_EQ(floating_workspace_service->GetLatestFloatingWorkspaceTemplate()
->created_time(),
second_captured_template_creation_time);
}
TEST_F(FloatingWorkspaceServiceV2Test, PopulateFloatingWorkspaceTemplate) {
PopulateAppsCache();
const std::string template_name = "floating_workspace_template";
base::RunLoop loop;
fake_desk_sync_service()->GetDeskModel()->AddOrUpdateEntry(
MakeTestFloatingWorkspaceDeskTemplate(template_name, base::Time::Now()),
base::BindLambdaForTesting(
[&](desks_storage::DeskModel::AddOrUpdateEntryStatus status,
std::unique_ptr<ash::DeskTemplate> new_entry) {
EXPECT_EQ(desks_storage::DeskModel::AddOrUpdateEntryStatus::kOk,
status);
loop.Quit();
}));
loop.Run();
CreateFloatingWorkspaceServiceForTesting(profile());
auto* floating_workspace_service =
FloatingWorkspaceService::GetForProfile(profile());
floating_workspace_service->Init(test_sync_service(),
fake_desk_sync_service(),
fake_device_info_sync_service());
test_sync_service()->SetDownloadStatusFor(
{syncer::DataType::WORKSPACE_DESK},
syncer::SyncService::DataTypeDownloadStatus::kUpToDate);
test_sync_service()->FireStateChanged();
EXPECT_EQ(
floating_workspace_service->GetFloatingWorkspaceTemplateEntries().size(),
1u);
}
TEST_F(FloatingWorkspaceServiceV2Test,
PopulateFloatingWorkspaceTemplateWithUpdates) {
PopulateAppsCache();
std::unique_ptr<ash::DeskTemplate> template_1 =
MakeTestFloatingWorkspaceDeskTemplate("Template 1", base::Time::Now());
base::Uuid template_1_uuid = template_1->uuid();
base::RunLoop loop;
fake_desk_sync_service()->GetDeskModel()->AddOrUpdateEntry(
std::move(template_1),
base::BindLambdaForTesting(
[&](desks_storage::DeskModel::AddOrUpdateEntryStatus status,
std::unique_ptr<ash::DeskTemplate> new_entry) {
EXPECT_EQ(desks_storage::DeskModel::AddOrUpdateEntryStatus::kOk,
status);
loop.Quit();
}));
loop.Run();
CreateFloatingWorkspaceServiceForTesting(profile());
auto* floating_workspace_service =
FloatingWorkspaceService::GetForProfile(profile());
floating_workspace_service->Init(test_sync_service(),
fake_desk_sync_service(),
fake_device_info_sync_service());
test_sync_service()->SetDownloadStatusFor(
{syncer::DataType::WORKSPACE_DESK},
syncer::SyncService::DataTypeDownloadStatus::kUpToDate);
test_sync_service()->FireStateChanged();
EXPECT_EQ(
floating_workspace_service->GetFloatingWorkspaceTemplateEntries().size(),
1u);
std::unique_ptr<ash::DeskTemplate> template_2 =
MakeTestFloatingWorkspaceDeskTemplate("Template 2", base::Time::Now());
base::Uuid template_2_uuid = template_2->uuid();
base::RunLoop loop2;
fake_desk_sync_service()->GetDeskModel()->AddOrUpdateEntry(
std::move(template_2),
base::BindLambdaForTesting(
[&](desks_storage::DeskModel::AddOrUpdateEntryStatus status,
std::unique_ptr<ash::DeskTemplate> new_entry) {
EXPECT_EQ(desks_storage::DeskModel::AddOrUpdateEntryStatus::kOk,
status);
loop2.Quit();
}));
loop2.Run();
base::RunLoop loop3;
fake_desk_sync_service()->GetDeskModel()->DeleteEntry(
template_1_uuid,
base::BindLambdaForTesting(
[&](desks_storage::DeskModel::DeleteEntryStatus status) {
EXPECT_EQ(desks_storage::DeskModel::DeleteEntryStatus::kOk, status);
loop3.Quit();
}));
loop3.Run();
test_sync_service()->SetDownloadStatusFor(
{syncer::DataType::WORKSPACE_DESK},
syncer::SyncService::DataTypeDownloadStatus::kUpToDate);
test_sync_service()->FireStateChanged();
EXPECT_EQ(
floating_workspace_service->GetFloatingWorkspaceTemplateEntries().size(),
1u);
test_sync_service()->SetDownloadStatusFor(
{syncer::DataType::WORKSPACE_DESK},
syncer::SyncService::DataTypeDownloadStatus::kUpToDate);
test_sync_service()->FireStateChanged();
EXPECT_EQ(floating_workspace_service->GetFloatingWorkspaceTemplateEntries()[0]
->uuid(),
template_2_uuid);
}
TEST_F(FloatingWorkspaceServiceV2Test,
DoNotPerformGarbageCollectionOnSingleEntryBeyondThreshold) {
PopulateAppsCache();
const std::string fws_name = "Template 1";
std::unique_ptr<ash::DeskTemplate> fws_template =
MakeTestFloatingWorkspaceDeskTemplate(fws_name, base::Time::Now());
fws_template->set_client_cache_guid("cache_guid_1");
base::RunLoop loop;
fake_desk_sync_service()->GetDeskModel()->AddOrUpdateEntry(
std::move(fws_template),
base::BindLambdaForTesting(
[&](desks_storage::DeskModel::AddOrUpdateEntryStatus status,
std::unique_ptr<ash::DeskTemplate> new_entry) {
EXPECT_EQ(desks_storage::DeskModel::AddOrUpdateEntryStatus::kOk,
status);
loop.Quit();
}));
loop.Run();
task_environment().AdvanceClock(base::Days(31));
CreateFloatingWorkspaceServiceForTesting(profile());
auto* floating_workspace_service =
FloatingWorkspaceService::GetForProfile(profile());
floating_workspace_service->Init(test_sync_service(),
fake_desk_sync_service(),
fake_device_info_sync_service());
test_sync_service()->SetDownloadStatusFor(
{syncer::DataType::WORKSPACE_DESK},
syncer::SyncService::DataTypeDownloadStatus::kUpToDate);
test_sync_service()->FireStateChanged();
ASSERT_TRUE(mock_desks_client()->restored_desk_template());
EXPECT_EQ(mock_desks_client()->restored_desk_template()->template_name(),
base::UTF8ToUTF16(fws_name));
EXPECT_EQ(
1ul, fake_desk_sync_service()->GetDeskModel()->GetAllEntryUuids().size());
}
TEST_F(FloatingWorkspaceServiceV2Test, PerformGarbageCollectionOnStaleEntries) {
PopulateAppsCache();
const std::string fws_one_name = "Template 1";
const std::string fws_two_name = "Template 2";
std::unique_ptr<ash::DeskTemplate> fws_one =
MakeTestFloatingWorkspaceDeskTemplate(fws_one_name, base::Time::Now());
fws_one->set_client_cache_guid("cache_guid_1");
std::unique_ptr<ash::DeskTemplate> fws_two =
MakeTestFloatingWorkspaceDeskTemplate(fws_two_name,
base::Time::Now() + base::Days(2));
fws_two->set_client_cache_guid("cache_guid_2");
base::RunLoop loop;
fake_desk_sync_service()->GetDeskModel()->AddOrUpdateEntry(
std::move(fws_one),
base::BindLambdaForTesting(
[&](desks_storage::DeskModel::AddOrUpdateEntryStatus status,
std::unique_ptr<ash::DeskTemplate> new_entry) {
EXPECT_EQ(desks_storage::DeskModel::AddOrUpdateEntryStatus::kOk,
status);
loop.Quit();
}));
loop.Run();
base::RunLoop loop2;
fake_desk_sync_service()->GetDeskModel()->AddOrUpdateEntry(
std::move(fws_two),
base::BindLambdaForTesting(
[&](desks_storage::DeskModel::AddOrUpdateEntryStatus status,
std::unique_ptr<ash::DeskTemplate> new_entry) {
EXPECT_EQ(desks_storage::DeskModel::AddOrUpdateEntryStatus::kOk,
status);
loop2.Quit();
}));
loop2.Run();
task_environment().AdvanceClock(base::Days(31));
CreateFloatingWorkspaceServiceForTesting(profile());
auto* floating_workspace_service =
FloatingWorkspaceService::GetForProfile(profile());
floating_workspace_service->Init(test_sync_service(),
fake_desk_sync_service(),
fake_device_info_sync_service());
test_sync_service()->SetDownloadStatusFor(
{syncer::DataType::WORKSPACE_DESK},
syncer::SyncService::DataTypeDownloadStatus::kUpToDate);
test_sync_service()->FireStateChanged();
ASSERT_TRUE(mock_desks_client()->restored_desk_template());
EXPECT_EQ(mock_desks_client()->restored_desk_template()->template_name(),
base::UTF8ToUTF16(fws_two_name));
EXPECT_EQ(
1ul, fake_desk_sync_service()->GetDeskModel()->GetAllEntryUuids().size());
}
TEST_F(FloatingWorkspaceServiceV2Test,
FloatingWorkspaceTemplateHasProgressStatus) {
PopulateAppsCache();
CreateFloatingWorkspaceServiceForTesting(profile());
auto* floating_workspace_service =
FloatingWorkspaceService::GetForProfile(profile());
floating_workspace_service->Init(test_sync_service(),
fake_desk_sync_service(),
fake_device_info_sync_service());
task_environment().FastForwardBy(base::Seconds(5));
EXPECT_TRUE(HasNotificationFor(kNotificationForProgressStatus));
// Wait for download to complete and check that the progress bar is gone.
test_sync_service()->SetDownloadStatusFor(
{syncer::DataType::WORKSPACE_DESK},
syncer::SyncService::DataTypeDownloadStatus::kUpToDate);
test_sync_service()->FireStateChanged();
EXPECT_FALSE(HasNotificationFor(kNotificationForProgressStatus));
}
TEST_F(FloatingWorkspaceServiceV2Test,
FloatingWorkspaceTemplateProgressStatusGoneAfterTimeOut) {
PopulateAppsCache();
CreateFloatingWorkspaceServiceForTesting(profile());
auto* floating_workspace_service =
FloatingWorkspaceService::GetForProfile(profile());
floating_workspace_service->Init(test_sync_service(),
fake_desk_sync_service(),
fake_device_info_sync_service());
task_environment().FastForwardBy(base::Seconds(5));
EXPECT_TRUE(HasNotificationFor(kNotificationForProgressStatus));
// Wait for timeout and check that the progress bar is gone.
task_environment().FastForwardBy(
ash::features::kFloatingWorkspaceV2MaxTimeAvailableForRestoreAfterLogin
.Get() +
base::Seconds(1));
EXPECT_FALSE(HasNotificationFor(kNotificationForProgressStatus));
EXPECT_TRUE(HasNotificationFor(kNotificationForSyncErrorOrTimeOut));
}
TEST_F(FloatingWorkspaceServiceV2Test,
FloatingWorkspaceTemplateProgressStatusGoneAfterSyncError) {
PopulateAppsCache();
CreateFloatingWorkspaceServiceForTesting(profile());
auto* floating_workspace_service =
FloatingWorkspaceService::GetForProfile(profile());
floating_workspace_service->Init(test_sync_service(),
fake_desk_sync_service(),
fake_device_info_sync_service());
task_environment().FastForwardBy(base::Seconds(5));
EXPECT_TRUE(HasNotificationFor(kNotificationForProgressStatus));
// Send sync error to service.
test_sync_service()->SetDownloadStatusFor(
{syncer::DataType::WORKSPACE_DESK},
syncer::SyncService::DataTypeDownloadStatus::kError);
test_sync_service()->FireStateChanged();
EXPECT_FALSE(HasNotificationFor(kNotificationForProgressStatus));
EXPECT_TRUE(HasNotificationFor(kNotificationForSyncErrorOrTimeOut));
}
TEST_F(FloatingWorkspaceServiceV2Test,
FloatingWorkspaceTemplateRestoreAfterTimeOutWithNewCapture) {
PopulateAppsCache();
const std::string template_name = "floating_workspace_template";
base::RunLoop loop;
fake_desk_sync_service()->GetDeskModel()->AddOrUpdateEntry(
MakeTestFloatingWorkspaceDeskTemplate(template_name, base::Time::Now()),
base::BindLambdaForTesting(
[&](desks_storage::DeskModel::AddOrUpdateEntryStatus status,
std::unique_ptr<ash::DeskTemplate> new_entry) {
EXPECT_EQ(desks_storage::DeskModel::AddOrUpdateEntryStatus::kOk,
status);
loop.Quit();
}));
loop.Run();
CreateFloatingWorkspaceServiceForTesting(profile());
auto* floating_workspace_service =
FloatingWorkspaceService::GetForProfile(profile());
floating_workspace_service->Init(test_sync_service(),
fake_desk_sync_service(),
fake_device_info_sync_service());
task_environment().FastForwardBy(
ash::features::kFloatingWorkspaceV2MaxTimeAvailableForRestoreAfterLogin
.Get() +
base::Seconds(1));
EXPECT_TRUE(HasNotificationFor(kNotificationForSyncErrorOrTimeOut));
test_sync_service()->SetDownloadStatusFor(
{syncer::DataType::WORKSPACE_DESK},
syncer::SyncService::DataTypeDownloadStatus::kUpToDate);
test_sync_service()->FireStateChanged();
EXPECT_FALSE(mock_desks_client()->restored_desk_template());
EXPECT_TRUE(HasNotificationFor(kNotificationForRestoreAfterError));
user_activity_detector()->set_last_activity_time_for_test(
base::TimeTicks::Now());
// Capture a new desk before user restores.
const std::string captured_template_name =
"floating_workspace_captured_template";
const base::Time captured_creation_time = base::Time::Now();
std::unique_ptr<DeskTemplate> captured_floating_workspace_template =
MakeTestFloatingWorkspaceDeskTemplate(captured_template_name,
captured_creation_time);
mock_desks_client()->SetCapturedDeskTemplate(
std::move(captured_floating_workspace_template));
task_environment().FastForwardBy(
ash::features::kFloatingWorkspaceV2PeriodicJobIntervalInSeconds.Get() +
base::Seconds(1));
EXPECT_TRUE(floating_workspace_service->GetLatestFloatingWorkspaceTemplate());
EXPECT_EQ(floating_workspace_service->GetLatestFloatingWorkspaceTemplate()
->created_time(),
captured_creation_time);
// User clicks restore button on the notification and we should the entry
// prior to the capture.
display_service()->SimulateClick(
NotificationHandler::Type::TRANSIENT, kNotificationForRestoreAfterError,
static_cast<int>(RestoreFromErrorNotificationButtonIndex::kRestore),
std::nullopt);
EXPECT_TRUE(mock_desks_client()->restored_desk_template());
EXPECT_EQ(mock_desks_client()->restored_desk_template()->template_name(),
base::UTF8ToUTF16(template_name));
}
TEST_F(FloatingWorkspaceServiceV2Test,
RestoreWhenNoFloatingWorkspaceTemplateIsAvailable) {
PopulateAppsCache();
CreateFloatingWorkspaceServiceForTesting(profile());
auto* floating_workspace_service =
FloatingWorkspaceService::GetForProfile(profile());
floating_workspace_service->Init(test_sync_service(),
fake_desk_sync_service(),
fake_device_info_sync_service());
test_sync_service()->SetDownloadStatusFor(
{syncer::DataType::WORKSPACE_DESK},
syncer::SyncService::DataTypeDownloadStatus::kUpToDate);
test_sync_service()->FireStateChanged();
EXPECT_FALSE(mock_desks_client()->restored_desk_template());
}
TEST_F(FloatingWorkspaceServiceV2Test, CanRecordTemplateNotFoundMetric) {
PopulateAppsCache();
base::HistogramTester histogram_tester;
CreateFloatingWorkspaceServiceForTesting(profile());
auto* floating_workspace_service =
FloatingWorkspaceService::GetForProfile(profile());
floating_workspace_service->Init(test_sync_service(),
fake_desk_sync_service(),
fake_device_info_sync_service());
test_sync_service()->SetDownloadStatusFor(
{syncer::DataType::WORKSPACE_DESK},
syncer::SyncService::DataTypeDownloadStatus::kUpToDate);
test_sync_service()->FireStateChanged();
EXPECT_FALSE(mock_desks_client()->restored_desk_template());
histogram_tester.ExpectTotalCount(
floating_workspace_metrics_util::kFloatingWorkspaceV2TemplateNotFound,
1u);
}
TEST_F(FloatingWorkspaceServiceV2Test, CanRecordFloatingWorkspaceV2InitMetric) {
PopulateAppsCache();
base::HistogramTester histogram_tester;
CreateFloatingWorkspaceServiceForTesting(profile());
auto* floating_workspace_service =
FloatingWorkspaceService::GetForProfile(profile());
floating_workspace_service->Init(test_sync_service(),
fake_desk_sync_service(),
fake_device_info_sync_service());
histogram_tester.ExpectTotalCount(
floating_workspace_metrics_util::kFloatingWorkspaceV2Initialized, 1u);
}
TEST_F(FloatingWorkspaceServiceV2Test,
CaptureButDontUploadIfNoUserActionAfterkUpToDate) {
// Upload should be executed if two captured templates are the different.
PopulateAppsCache();
user_activity_detector()->set_last_activity_time_for_test(
base::TimeTicks::Now());
// Idle for a while.
task_environment().FastForwardBy(
ash::features::kFloatingWorkspaceV2PeriodicJobIntervalInSeconds.Get() +
base::Seconds(1));
CreateFloatingWorkspaceServiceForTesting(profile());
auto* floating_workspace_service =
FloatingWorkspaceService::GetForProfile(profile());
floating_workspace_service->Init(test_sync_service(),
fake_desk_sync_service(),
fake_device_info_sync_service());
test_sync_service()->SetDownloadStatusFor(
{syncer::DataType::WORKSPACE_DESK},
syncer::SyncService::DataTypeDownloadStatus::kUpToDate);
test_sync_service()->FireStateChanged();
const std::string template_name = "floating_workspace_captured_template";
const base::Time first_captured_template_creation_time = base::Time::Now();
std::unique_ptr<DeskTemplate> first_captured_floating_workspace_template =
MakeTestFloatingWorkspaceDeskTemplate(
template_name, first_captured_template_creation_time);
mock_desks_client()->SetCapturedDeskTemplate(
std::move(first_captured_floating_workspace_template));
// Trigger the first capture task.
task_environment().FastForwardBy(
ash::features::kFloatingWorkspaceV2PeriodicJobIntervalInSeconds.Get() +
base::Seconds(1));
EXPECT_FALSE(
floating_workspace_service->GetLatestFloatingWorkspaceTemplate());
}
TEST_F(FloatingWorkspaceServiceV2Test,
WaitForAppCacheBeforeRestoringFloatingWorkspaceTemplate) {
apps::AppRegistryCacheWrapper& wrapper = apps::AppRegistryCacheWrapper::Get();
wrapper.RemoveAppRegistryCache(cache());
const std::string template_name = "floating_workspace_template";
base::RunLoop loop;
fake_desk_sync_service()->GetDeskModel()->AddOrUpdateEntry(
MakeTestFloatingWorkspaceDeskTemplate(template_name, base::Time::Now()),
base::BindLambdaForTesting(
[&](desks_storage::DeskModel::AddOrUpdateEntryStatus status,
std::unique_ptr<ash::DeskTemplate> new_entry) {
EXPECT_EQ(desks_storage::DeskModel::AddOrUpdateEntryStatus::kOk,
status);
loop.Quit();
}));
loop.Run();
CreateFloatingWorkspaceServiceForTesting(profile());
auto* floating_workspace_service =
FloatingWorkspaceService::GetForProfile(profile());
floating_workspace_service->Init(test_sync_service(),
fake_desk_sync_service(),
fake_device_info_sync_service());
wrapper.AddAppRegistryCache(account_id(), cache());
test_sync_service()->SetDownloadStatusFor(
{syncer::DataType::WORKSPACE_DESK},
syncer::SyncService::DataTypeDownloadStatus::kUpToDate);
test_sync_service()->FireStateChanged();
EXPECT_FALSE(mock_desks_client()->restored_desk_template());
PopulateAppsCache();
EXPECT_EQ(mock_desks_client()->restored_desk_template()->template_name(),
base::UTF8ToUTF16(template_name));
}
TEST_F(FloatingWorkspaceServiceV2Test,
CaptureButDontUploadIfNoUserActionAfterLastUpload) {
// Upload should be executed if two captured templates are the different.
PopulateAppsCache();
// Idle for a while.
task_environment().FastForwardBy(
ash::features::kFloatingWorkspaceV2PeriodicJobIntervalInSeconds.Get() +
base::Seconds(1));
CreateFloatingWorkspaceServiceForTesting(profile());
auto* floating_workspace_service =
FloatingWorkspaceService::GetForProfile(profile());
floating_workspace_service->Init(test_sync_service(),
fake_desk_sync_service(),
fake_device_info_sync_service());
test_sync_service()->SetDownloadStatusFor(
{syncer::DataType::WORKSPACE_DESK},
syncer::SyncService::DataTypeDownloadStatus::kUpToDate);
test_sync_service()->FireStateChanged();
const std::string template_name = "floating_workspace_captured_template";
const base::Time first_captured_template_creation_time = base::Time::Now();
std::unique_ptr<DeskTemplate> first_captured_floating_workspace_template =
MakeTestFloatingWorkspaceDeskTemplate(
template_name, first_captured_template_creation_time);
mock_desks_client()->SetCapturedDeskTemplate(
std::move(first_captured_floating_workspace_template));
user_activity_detector()->set_last_activity_time_for_test(
base::TimeTicks::Now());
// Trigger the first capture task.
task_environment().FastForwardBy(
ash::features::kFloatingWorkspaceV2PeriodicJobIntervalInSeconds.Get() +
base::Seconds(1));
EXPECT_TRUE(floating_workspace_service->GetLatestFloatingWorkspaceTemplate());
const std::string template_name2 = "floating_workspace_captured_template_2";
const base::Time second_captured_template_creation_time = base::Time::Now();
std::unique_ptr<DeskTemplate> second_captured_floating_workspace_template =
MakeTestFloatingWorkspaceDeskTemplate(
template_name2, second_captured_template_creation_time);
mock_desks_client()->SetCapturedDeskTemplate(
std::move(second_captured_floating_workspace_template));
// Trigger the second capture task.
task_environment().FastForwardBy(
ash::features::kFloatingWorkspaceV2PeriodicJobIntervalInSeconds.Get() +
base::Seconds(1));
EXPECT_TRUE(floating_workspace_service->GetLatestFloatingWorkspaceTemplate()
->template_name() != base::UTF8ToUTF16(template_name2));
}
TEST_F(FloatingWorkspaceServiceV2Test, CaptureImmediatelyAfterRestore) {
PopulateAppsCache();
const std::string template_name = "floating_workspace_template";
base::RunLoop loop;
fake_desk_sync_service()->GetDeskModel()->AddOrUpdateEntry(
MakeTestFloatingWorkspaceDeskTemplate(template_name, base::Time::Now()),
base::BindLambdaForTesting(
[&](desks_storage::DeskModel::AddOrUpdateEntryStatus status,
std::unique_ptr<ash::DeskTemplate> new_entry) {
EXPECT_EQ(desks_storage::DeskModel::AddOrUpdateEntryStatus::kOk,
status);
loop.Quit();
}));
loop.Run();
CreateFloatingWorkspaceServiceForTesting(profile());
auto* floating_workspace_service =
FloatingWorkspaceService::GetForProfile(profile());
floating_workspace_service->Init(test_sync_service(),
fake_desk_sync_service(),
fake_device_info_sync_service());
const base::Time creation_time = base::Time::Now();
std::unique_ptr<DeskTemplate> floating_workspace_template =
MakeTestFloatingWorkspaceDeskTemplate(template_name, creation_time);
mock_desks_client()->SetCapturedDeskTemplate(
std::move(floating_workspace_template));
test_sync_service()->SetDownloadStatusFor(
{syncer::DataType::WORKSPACE_DESK},
syncer::SyncService::DataTypeDownloadStatus::kUpToDate);
test_sync_service()->FireStateChanged();
user_activity_detector()->set_last_activity_time_for_test(
base::TimeTicks::Now() + base::Milliseconds(1));
ASSERT_TRUE(mock_desks_client()->restored_desk_template());
EXPECT_EQ(mock_desks_client()->restored_desk_template()->template_name(),
base::UTF8ToUTF16(template_name));
ASSERT_TRUE(floating_workspace_service->GetLatestFloatingWorkspaceTemplate());
EXPECT_EQ(floating_workspace_service->GetLatestFloatingWorkspaceTemplate()
->created_time(),
creation_time);
}
TEST_F(FloatingWorkspaceServiceV2Test,
CaptureFloatingWorkspaceTemplateOnSystemTrayVisible) {
PopulateAppsCache();
CreateFloatingWorkspaceServiceForTesting(profile());
auto* floating_workspace_service =
FloatingWorkspaceService::GetForProfile(profile());
floating_workspace_service->Init(test_sync_service(),
fake_desk_sync_service(),
fake_device_info_sync_service());
const std::string template_name = "floating_workspace_captured_template";
const base::Time creation_time = base::Time::Now();
std::unique_ptr<DeskTemplate> floating_workspace_template =
MakeTestFloatingWorkspaceDeskTemplate(template_name, creation_time);
test_sync_service()->SetDownloadStatusFor(
{syncer::DataType::WORKSPACE_DESK},
syncer::SyncService::DataTypeDownloadStatus::kUpToDate);
test_sync_service()->FireStateChanged();
mock_desks_client()->SetCapturedDeskTemplate(
std::move(floating_workspace_template));
ash::Shell::Get()->system_tray_notifier()->NotifySystemTrayBubbleShown();
ASSERT_TRUE(floating_workspace_service->GetLatestFloatingWorkspaceTemplate());
EXPECT_EQ(floating_workspace_service->GetLatestFloatingWorkspaceTemplate()
->created_time(),
creation_time);
}
TEST_F(FloatingWorkspaceServiceV2Test,
CaptureFloatingWorkspaceTemplateOnLockScreen) {
SessionControllerClientImpl client;
client.Init();
PopulateAppsCache();
CreateFloatingWorkspaceServiceForTesting(profile());
auto* floating_workspace_service =
FloatingWorkspaceService::GetForProfile(profile());
floating_workspace_service->Init(test_sync_service(),
fake_desk_sync_service(),
fake_device_info_sync_service());
const std::string template_name = "floating_workspace_captured_template";
const base::Time creation_time = base::Time::Now();
std::unique_ptr<DeskTemplate> floating_workspace_template =
MakeTestFloatingWorkspaceDeskTemplate(template_name, creation_time);
test_sync_service()->SetDownloadStatusFor(
{syncer::DataType::WORKSPACE_DESK},
syncer::SyncService::DataTypeDownloadStatus::kUpToDate);
test_sync_service()->FireStateChanged();
user_activity_detector()->set_last_activity_time_for_test(
base::TimeTicks::Now());
// Set the captured desk template after the sync service has fire the
// `kUpToDate` signal. This is because a capture and upload happens after the
// fire event. We want to instead set the captured template after this so we
// can test that a new template was captured and uploaded.
mock_desks_client()->SetCapturedDeskTemplate(
std::move(floating_workspace_template));
base::RunLoop run_loop;
client.PrepareForLock(run_loop.QuitClosure());
run_loop.Run();
ASSERT_TRUE(floating_workspace_service->GetLatestFloatingWorkspaceTemplate());
EXPECT_EQ(floating_workspace_service->GetLatestFloatingWorkspaceTemplate()
->created_time(),
creation_time);
}
TEST_F(FloatingWorkspaceServiceV2Test,
RestoreFloatingWorkspaceTemplateAfterWakingUpFromSleep) {
PopulateAppsCache();
const std::string template_name = "floating_workspace_template";
base::RunLoop loop;
fake_desk_sync_service()->GetDeskModel()->AddOrUpdateEntry(
MakeTestFloatingWorkspaceDeskTemplate(template_name, base::Time::Now()),
base::BindLambdaForTesting(
[&](desks_storage::DeskModel::AddOrUpdateEntryStatus status,
std::unique_ptr<ash::DeskTemplate> new_entry) {
EXPECT_EQ(desks_storage::DeskModel::AddOrUpdateEntryStatus::kOk,
status);
loop.Quit();
}));
loop.Run();
CreateFloatingWorkspaceServiceForTesting(profile());
auto* floating_workspace_service =
FloatingWorkspaceService::GetForProfile(profile());
floating_workspace_service->Init(test_sync_service(),
fake_desk_sync_service(),
fake_device_info_sync_service());
test_sync_service()->SetDownloadStatusFor(
{syncer::DataType::WORKSPACE_DESK},
syncer::SyncService::DataTypeDownloadStatus::kUpToDate);
test_sync_service()->FireStateChanged();
ASSERT_TRUE(mock_desks_client()->restored_desk_template());
EXPECT_EQ(mock_desks_client()->restored_desk_template()->template_name(),
base::UTF8ToUTF16(template_name));
// Send device to sleep. Add a newly captured floating workspace template.
power_manager_client()->SendSuspendImminent(
power_manager::SuspendImminent_Reason_OTHER);
const std::string new_template_name = "floating_workspace_captured_template";
const base::Time creation_time = base::Time::Now() + base::Seconds(1);
std::unique_ptr<DeskTemplate> new_floating_workspace_template =
MakeTestFloatingWorkspaceDeskTemplate(new_template_name, creation_time);
base::RunLoop loop2;
fake_desk_sync_service()->GetDeskModel()->AddOrUpdateEntry(
new_floating_workspace_template->Clone(),
base::BindLambdaForTesting(
[&](desks_storage::DeskModel::AddOrUpdateEntryStatus status,
std::unique_ptr<ash::DeskTemplate> new_entry) {
EXPECT_EQ(desks_storage::DeskModel::AddOrUpdateEntryStatus::kOk,
status);
loop2.Quit();
}));
loop2.Run();
// Wake device up. Go through normal restore flow.
power_manager_client()->SendSuspendDone();
floating_workspace_service->OnLockStateChanged(/*locked=*/false);
test_sync_service()->SetDownloadStatusFor(
{syncer::DataType::WORKSPACE_DESK},
syncer::SyncService::DataTypeDownloadStatus::kWaitingForUpdates);
test_sync_service()->FireStateChanged();
test_sync_service()->SetDownloadStatusFor(
{syncer::DataType::WORKSPACE_DESK},
syncer::SyncService::DataTypeDownloadStatus::kUpToDate);
test_sync_service()->FireStateChanged();
ASSERT_TRUE(mock_desks_client()->restored_template_uuid().is_valid());
EXPECT_EQ(mock_desks_client()->restored_template_uuid(),
new_floating_workspace_template->uuid());
}
TEST_F(FloatingWorkspaceServiceV2Test,
RestoreFloatingWorkspaceTemplateTimeoutAfterWakingFromSleep) {
PopulateAppsCache();
const std::string template_name = "floating_workspace_template";
base::RunLoop loop;
fake_desk_sync_service()->GetDeskModel()->AddOrUpdateEntry(
MakeTestFloatingWorkspaceDeskTemplate(template_name, base::Time::Now()),
base::BindLambdaForTesting(
[&](desks_storage::DeskModel::AddOrUpdateEntryStatus status,
std::unique_ptr<ash::DeskTemplate> new_entry) {
EXPECT_EQ(desks_storage::DeskModel::AddOrUpdateEntryStatus::kOk,
status);
loop.Quit();
}));
loop.Run();
CreateFloatingWorkspaceServiceForTesting(profile());
auto* floating_workspace_service =
FloatingWorkspaceService::GetForProfile(profile());
floating_workspace_service->Init(test_sync_service(),
fake_desk_sync_service(),
fake_device_info_sync_service());
test_sync_service()->SetDownloadStatusFor(
{syncer::DataType::WORKSPACE_DESK},
syncer::SyncService::DataTypeDownloadStatus::kUpToDate);
test_sync_service()->FireStateChanged();
ASSERT_TRUE(mock_desks_client()->restored_desk_template());
EXPECT_EQ(mock_desks_client()->restored_desk_template()->template_name(),
base::UTF8ToUTF16(template_name));
// Send device to sleep. Add a newly captured floating workspace template.
power_manager_client()->SendSuspendImminent(
power_manager::SuspendImminent_Reason_OTHER);
const std::string new_template_name = "floating_workspace_captured_template";
const base::Time creation_time = base::Time::Now() + base::Seconds(1);
// Wake device up. Go through normal restore flow.
power_manager_client()->SendSuspendDone();
floating_workspace_service->OnLockStateChanged(/*locked=*/false);
task_environment().FastForwardBy(base::Seconds(3));
test_sync_service()->SetDownloadStatusFor(
{syncer::DataType::WORKSPACE_DESK},
syncer::SyncService::DataTypeDownloadStatus::kWaitingForUpdates);
test_sync_service()->FireStateChanged();
task_environment().FastForwardBy(
ash::features::kFloatingWorkspaceV2MaxTimeAvailableForRestoreAfterLogin
.Get());
EXPECT_TRUE(HasNotificationFor(kNotificationForSyncErrorOrTimeOut));
std::unique_ptr<DeskTemplate> new_floating_workspace_template =
MakeTestFloatingWorkspaceDeskTemplate(new_template_name, creation_time);
base::RunLoop loop2;
fake_desk_sync_service()->GetDeskModel()->AddOrUpdateEntry(
new_floating_workspace_template->Clone(),
base::BindLambdaForTesting(
[&](desks_storage::DeskModel::AddOrUpdateEntryStatus status,
std::unique_ptr<ash::DeskTemplate> new_entry) {
EXPECT_EQ(desks_storage::DeskModel::AddOrUpdateEntryStatus::kOk,
status);
loop2.Quit();
}));
loop2.Run();
test_sync_service()->SetDownloadStatusFor(
{syncer::DataType::WORKSPACE_DESK},
syncer::SyncService::DataTypeDownloadStatus::kWaitingForUpdates);
test_sync_service()->FireStateChanged();
test_sync_service()->SetDownloadStatusFor(
{syncer::DataType::WORKSPACE_DESK},
syncer::SyncService::DataTypeDownloadStatus::kUpToDate);
test_sync_service()->FireStateChanged();
EXPECT_TRUE(HasNotificationFor(kNotificationForRestoreAfterError));
// User clicks restore button on the notification.
display_service()->SimulateClick(
NotificationHandler::Type::TRANSIENT, kNotificationForRestoreAfterError,
static_cast<int>(RestoreFromErrorNotificationButtonIndex::kRestore),
std::nullopt);
ASSERT_TRUE(mock_desks_client()->restored_template_uuid().is_valid());
EXPECT_EQ(mock_desks_client()->restored_template_uuid(),
new_floating_workspace_template->uuid());
}
TEST_F(
FloatingWorkspaceServiceV2Test,
RestoreFloatingWorkspaceTemplateNoTimeoutAfterWakingFromSleepWithNoNewEntry) {
PopulateAppsCache();
const std::string template_name = "floating_workspace_template";
base::RunLoop loop;
fake_desk_sync_service()->GetDeskModel()->AddOrUpdateEntry(
MakeTestFloatingWorkspaceDeskTemplate(template_name, base::Time::Now()),
base::BindLambdaForTesting(
[&](desks_storage::DeskModel::AddOrUpdateEntryStatus status,
std::unique_ptr<ash::DeskTemplate> new_entry) {
EXPECT_EQ(desks_storage::DeskModel::AddOrUpdateEntryStatus::kOk,
status);
loop.Quit();
}));
loop.Run();
CreateFloatingWorkspaceServiceForTesting(profile());
auto* floating_workspace_service =
FloatingWorkspaceService::GetForProfile(profile());
floating_workspace_service->Init(test_sync_service(),
fake_desk_sync_service(),
fake_device_info_sync_service());
test_sync_service()->SetDownloadStatusFor(
{syncer::DataType::WORKSPACE_DESK},
syncer::SyncService::DataTypeDownloadStatus::kUpToDate);
test_sync_service()->FireStateChanged();
ASSERT_TRUE(mock_desks_client()->restored_desk_template());
EXPECT_EQ(mock_desks_client()->restored_desk_template()->template_name(),
base::UTF8ToUTF16(template_name));
// Send device to sleep.
power_manager_client()->SendSuspendImminent(
power_manager::SuspendImminent_Reason_OTHER);
// Wake device up. Go through normal restore flow.
power_manager_client()->SendSuspendDone();
floating_workspace_service->OnLockStateChanged(/*locked=*/false);
task_environment().FastForwardBy(
ash::features::kFloatingWorkspaceV2MaxTimeAvailableForRestoreAfterLogin
.Get() +
base::Seconds(1));
test_sync_service()->SetDownloadStatusFor(
{syncer::DataType::WORKSPACE_DESK},
syncer::SyncService::DataTypeDownloadStatus::kUpToDate);
test_sync_service()->FireStateChanged();
EXPECT_FALSE(HasNotificationFor(kNotificationForRestoreAfterError));
ASSERT_TRUE(mock_desks_client()->restored_desk_template());
EXPECT_EQ(mock_desks_client()->restored_desk_template()->template_name(),
base::UTF8ToUTF16(template_name));
}
TEST_F(FloatingWorkspaceServiceV2Test,
FloatingWorkspaceTemplateRestoreAfterTimeOutNotificationFromSleep) {
PopulateAppsCache();
const std::string template_name = "floating_workspace_template";
base::RunLoop loop;
fake_desk_sync_service()->GetDeskModel()->AddOrUpdateEntry(
MakeTestFloatingWorkspaceDeskTemplate(template_name, base::Time::Now()),
base::BindLambdaForTesting(
[&](desks_storage::DeskModel::AddOrUpdateEntryStatus status,
std::unique_ptr<ash::DeskTemplate> new_entry) {
EXPECT_EQ(desks_storage::DeskModel::AddOrUpdateEntryStatus::kOk,
status);
loop.Quit();
}));
loop.Run();
CreateFloatingWorkspaceServiceForTesting(profile());
auto* floating_workspace_service =
FloatingWorkspaceService::GetForProfile(profile());
floating_workspace_service->Init(test_sync_service(),
fake_desk_sync_service(),
fake_device_info_sync_service());
test_sync_service()->SetDownloadStatusFor(
{syncer::DataType::WORKSPACE_DESK},
syncer::SyncService::DataTypeDownloadStatus::kUpToDate);
test_sync_service()->FireStateChanged();
ASSERT_TRUE(mock_desks_client()->restored_desk_template());
EXPECT_EQ(mock_desks_client()->restored_desk_template()->template_name(),
base::UTF8ToUTF16(template_name));
// Send device to sleep.
power_manager_client()->SendSuspendImminent(
power_manager::SuspendImminent_Reason_OTHER);
// Wake device up. Go through normal restore flow.
power_manager_client()->SendSuspendDone();
floating_workspace_service->OnLockStateChanged(/*locked=*/false);
task_environment().FastForwardBy(base::Seconds(3));
test_sync_service()->SetDownloadStatusFor(
{syncer::DataType::WORKSPACE_DESK},
syncer::SyncService::DataTypeDownloadStatus::kWaitingForUpdates);
test_sync_service()->FireStateChanged();
task_environment().FastForwardBy(
ash::features::kFloatingWorkspaceV2MaxTimeAvailableForRestoreAfterLogin
.Get() +
base::Seconds(1));
EXPECT_TRUE(HasNotificationFor(kNotificationForSyncErrorOrTimeOut));
}
TEST_F(FloatingWorkspaceServiceV2Test,
DoNotShowTimeOutNotificationAfterRestoreTimeoutFromSuspend) {
PopulateAppsCache();
const std::string template_name = "floating_workspace_template";
base::RunLoop loop;
fake_desk_sync_service()->GetDeskModel()->AddOrUpdateEntry(
MakeTestFloatingWorkspaceDeskTemplate(template_name, base::Time::Now()),
base::BindLambdaForTesting(
[&](desks_storage::DeskModel::AddOrUpdateEntryStatus status,
std::unique_ptr<ash::DeskTemplate> new_entry) {
EXPECT_EQ(desks_storage::DeskModel::AddOrUpdateEntryStatus::kOk,
status);
loop.Quit();
}));
loop.Run();
CreateFloatingWorkspaceServiceForTesting(profile());
auto* floating_workspace_service =
FloatingWorkspaceService::GetForProfile(profile());
floating_workspace_service->Init(test_sync_service(),
fake_desk_sync_service(),
fake_device_info_sync_service());
test_sync_service()->SetDownloadStatusFor(
{syncer::DataType::WORKSPACE_DESK},
syncer::SyncService::DataTypeDownloadStatus::kUpToDate);
test_sync_service()->FireStateChanged();
ASSERT_TRUE(mock_desks_client()->restored_desk_template());
EXPECT_EQ(mock_desks_client()->restored_desk_template()->template_name(),
base::UTF8ToUTF16(template_name));
// Send device to sleep.
power_manager_client()->SendSuspendImminent(
power_manager::SuspendImminent_Reason_OTHER);
// Wake device up. Go through normal restore flow.
power_manager_client()->SendSuspendDone();
floating_workspace_service->OnLockStateChanged(/*locked=*/false);
task_environment().FastForwardBy(base::Seconds(3));
task_environment().FastForwardBy(
ash::features::kFloatingWorkspaceV2MaxTimeAvailableForRestoreAfterLogin
.Get() +
base::Seconds(1));
EXPECT_FALSE(HasNotificationFor(kNotificationForSyncErrorOrTimeOut));
}
TEST_F(FloatingWorkspaceServiceV2Test, AutoSignoutWithDeviceInfo) {
PopulateAppsCache();
base::RunLoop loop;
CreateFloatingWorkspaceServiceForTesting(profile());
auto* floating_workspace_service =
FloatingWorkspaceService::GetForProfile(profile());
floating_workspace_service->Init(test_sync_service(),
fake_desk_sync_service(),
fake_device_info_sync_service());
test_sync_service()->SetDownloadStatusFor(
{syncer::DataType::WORKSPACE_DESK},
syncer::SyncService::DataTypeDownloadStatus::kUpToDate);
test_sync_service()->FireStateChanged();
fake_device_info_sync_service()->GetDeviceInfoTracker()->Add(
CreateFakeDeviceInfo("guid1", "device1",
base::Time::Now() + base::Seconds(10)));
test_sync_service()->SetDownloadStatusFor(
{syncer::DataType::DEVICE_INFO},
syncer::SyncService::DataTypeDownloadStatus::kUpToDate);
test_sync_service()->FireStateChanged();
EXPECT_EQ(ash_test_helper()
->test_session_controller_client()
->request_sign_out_count(),
1);
}
TEST_F(FloatingWorkspaceServiceV2Test,
AutoSignoutDontTriggerWithSameDeviceInfoGuid) {
PopulateAppsCache();
const std::string template_name = "floating_workspace_template";
base::RunLoop loop;
CreateFloatingWorkspaceServiceForTesting(profile());
auto* floating_workspace_service =
FloatingWorkspaceService::GetForProfile(profile());
floating_workspace_service->Init(test_sync_service(),
fake_desk_sync_service(),
fake_device_info_sync_service());
test_sync_service()->SetDownloadStatusFor(
{syncer::DataType::WORKSPACE_DESK},
syncer::SyncService::DataTypeDownloadStatus::kUpToDate);
test_sync_service()->FireStateChanged();
fake_device_info_sync_service()->GetDeviceInfoTracker()->Add(
CreateFakeDeviceInfo("guid1", "device1",
base::Time::Now() + base::Seconds(10)));
fake_device_info_sync_service()->GetDeviceInfoTracker()->SetLocalCacheGuid(
"guid1");
test_sync_service()->SetDownloadStatusFor(
{syncer::DataType::DEVICE_INFO},
syncer::SyncService::DataTypeDownloadStatus::kUpToDate);
test_sync_service()->FireStateChanged();
EXPECT_EQ(ash_test_helper()
->test_session_controller_client()
->request_sign_out_count(),
0);
}
TEST_F(FloatingWorkspaceServiceV2Test,
AutoSignoutDontTriggerWithOldDeviceInfo) {
PopulateAppsCache();
const std::string template_name = "floating_workspace_template";
base::RunLoop loop;
CreateFloatingWorkspaceServiceForTesting(profile());
auto* floating_workspace_service =
FloatingWorkspaceService::GetForProfile(profile());
floating_workspace_service->Init(test_sync_service(),
fake_desk_sync_service(),
fake_device_info_sync_service());
test_sync_service()->SetDownloadStatusFor(
{syncer::DataType::WORKSPACE_DESK},
syncer::SyncService::DataTypeDownloadStatus::kUpToDate);
test_sync_service()->FireStateChanged();
fake_device_info_sync_service()->GetDeviceInfoTracker()->Add(
CreateFakeDeviceInfo("guid1", "device1",
base::Time::Now() - base::Seconds(10)));
test_sync_service()->SetDownloadStatusFor(
{syncer::DataType::DEVICE_INFO},
syncer::SyncService::DataTypeDownloadStatus::kUpToDate);
test_sync_service()->FireStateChanged();
EXPECT_EQ(ash_test_helper()
->test_session_controller_client()
->request_sign_out_count(),
0);
}
TEST_F(FloatingWorkspaceServiceV2Test, AutoSignoutWithWorkspaceDesk) {
// Upload should be executed if two captured templates are the different.
PopulateAppsCache();
CreateFloatingWorkspaceServiceForTesting(profile());
auto* floating_workspace_service =
FloatingWorkspaceService::GetForProfile(profile());
floating_workspace_service->Init(test_sync_service(),
fake_desk_sync_service(),
fake_device_info_sync_service());
const std::string template_name = "floating_workspace_captured_template";
const base::Time creation_time = base::Time::Now();
std::unique_ptr<DeskTemplate> floating_workspace_template =
MakeTestFloatingWorkspaceDeskTemplate(template_name, creation_time);
mock_desks_client()->SetCapturedDeskTemplate(
std::move(floating_workspace_template));
test_sync_service()->SetDownloadStatusFor(
{syncer::DataType::WORKSPACE_DESK},
syncer::SyncService::DataTypeDownloadStatus::kUpToDate);
test_sync_service()->FireStateChanged();
task_environment().FastForwardBy(
ash::features::kFloatingWorkspaceV2PeriodicJobIntervalInSeconds.Get() +
base::Seconds(1));
user_activity_detector()->set_last_activity_time_for_test(
base::TimeTicks::Now());
base::RunLoop loop;
fake_desk_sync_service()->GetDeskModel()->AddOrUpdateEntry(
MakeTestFloatingWorkspaceDeskTemplate(
template_name,
base::Time::Now() +
ash::features::kFloatingWorkspaceV2PeriodicJobIntervalInSeconds
.Get() +
base::Seconds(1)),
base::BindLambdaForTesting(
[&](desks_storage::DeskModel::AddOrUpdateEntryStatus status,
std::unique_ptr<ash::DeskTemplate> new_entry) {
EXPECT_EQ(desks_storage::DeskModel::AddOrUpdateEntryStatus::kOk,
status);
loop.Quit();
}));
loop.Run();
test_sync_service()->SetDownloadStatusFor(
{syncer::DataType::WORKSPACE_DESK},
syncer::SyncService::DataTypeDownloadStatus::kUpToDate);
test_sync_service()->FireStateChanged();
EXPECT_TRUE(floating_workspace_service->GetLatestFloatingWorkspaceTemplate());
EXPECT_EQ(ash_test_helper()
->test_session_controller_client()
->request_sign_out_count(),
1);
}
TEST_F(FloatingWorkspaceServiceV2Test,
AutoSignoutDontTriggerWithStaleWorkspaceDesk) {
// Upload should be executed if two captured templates are the different.
PopulateAppsCache();
CreateFloatingWorkspaceServiceForTesting(profile());
auto* floating_workspace_service =
FloatingWorkspaceService::GetForProfile(profile());
floating_workspace_service->Init(test_sync_service(),
fake_desk_sync_service(),
fake_device_info_sync_service());
const std::string template_name = "floating_workspace_captured_template";
const base::Time creation_time = base::Time::Now();
std::unique_ptr<DeskTemplate> floating_workspace_template =
MakeTestFloatingWorkspaceDeskTemplate(template_name, creation_time);
mock_desks_client()->SetCapturedDeskTemplate(
std::move(floating_workspace_template));
test_sync_service()->SetDownloadStatusFor(
{syncer::DataType::WORKSPACE_DESK},
syncer::SyncService::DataTypeDownloadStatus::kUpToDate);
test_sync_service()->FireStateChanged();
task_environment().FastForwardBy(
ash::features::kFloatingWorkspaceV2PeriodicJobIntervalInSeconds.Get() +
base::Seconds(1));
user_activity_detector()->set_last_activity_time_for_test(
base::TimeTicks::Now());
base::RunLoop loop;
fake_desk_sync_service()->GetDeskModel()->AddOrUpdateEntry(
MakeTestFloatingWorkspaceDeskTemplate(
template_name,
base::Time::Now() -
ash::features::kFloatingWorkspaceV2PeriodicJobIntervalInSeconds
.Get() -
base::Seconds(1)),
base::BindLambdaForTesting(
[&](desks_storage::DeskModel::AddOrUpdateEntryStatus status,
std::unique_ptr<ash::DeskTemplate> new_entry) {
EXPECT_EQ(desks_storage::DeskModel::AddOrUpdateEntryStatus::kOk,
status);
loop.Quit();
}));
loop.Run();
test_sync_service()->SetDownloadStatusFor(
{syncer::DataType::WORKSPACE_DESK},
syncer::SyncService::DataTypeDownloadStatus::kUpToDate);
test_sync_service()->FireStateChanged();
EXPECT_TRUE(floating_workspace_service->GetLatestFloatingWorkspaceTemplate());
EXPECT_EQ(ash_test_helper()
->test_session_controller_client()
->request_sign_out_count(),
0);
}
class FloatingWorkspaceServiceMultiUserTest
: public FloatingWorkspaceServiceV2Test {
protected:
FloatingWorkspaceServiceMultiUserTest() = default;
~FloatingWorkspaceServiceMultiUserTest() override { profile2_ = nullptr; }
TestingProfile* profile2() { return profile2_.get(); }
AccountId account_id2() { return account_id2_; }
apps::AppRegistryCache* cache2() { return cache2_.get(); }
desks_storage::FakeDeskSyncService* fake_desk_sync_service2() {
return fake_desk_sync_service2_.get();
}
syncer::TestSyncService* test_sync_service2() {
return test_sync_service2_.get();
}
syncer::FakeDeviceInfoSyncService* fake_device_info_sync_service2() {
return fake_device_info_sync_service2_.get();
}
void PopulateAppsCache2() {
desks_storage::desk_test_util::PopulateFloatingWorkspaceAppRegistryCache(
account_id2_, cache2_.get());
task_environment().RunUntilIdle();
}
void SetUp() override {
FloatingWorkspaceServiceV2Test::SetUp();
EXPECT_TRUE(temp_dir2_.CreateUniqueTempDir());
auto prefs =
std::make_unique<sync_preferences::TestingPrefServiceSyncable>();
RegisterUserProfilePrefs(prefs->registry());
profile2_ = profile_manager()->CreateTestingProfile(
kTestAccount2, std::move(prefs), std::u16string(),
/*avatar_id=*/0, TestingProfile::TestingFactories());
account_id2_ = AccountId::FromUserEmail(kTestAccount2);
const std::string username_hash2 =
user_manager::FakeUserManager::GetFakeUsernameHash(account_id2_);
fake_user_manager()->AddUser(account_id2_);
fake_user_manager()->UserLoggedIn(account_id2_, username_hash2,
/*browser_restart=*/false,
/*is_child=*/false);
CoreAccountInfo account_info;
account_info.email = kTestAccount2;
account_info.gaia = "gaia2";
account_info.account_id = CoreAccountId::FromGaiaId(account_info.gaia);
test_sync_service()->SetSignedIn(signin::ConsentLevel::kSync, account_info);
fake_desk_sync_service2_ =
std::make_unique<desks_storage::FakeDeskSyncService>(
/*skip_engine_connection=*/true);
test_sync_service2_ = std::make_unique<syncer::TestSyncService>();
display_service2_ =
std::make_unique<NotificationDisplayServiceTester>(profile2_.get());
cache2_ = std::make_unique<apps::AppRegistryCache>();
fake_device_info_sync_service2_ =
std::make_unique<syncer::FakeDeviceInfoSyncService>(
/*skip_engine_connection=*/true);
apps::AppRegistryCacheWrapper::Get().AddAppRegistryCache(account_id2_,
cache2_.get());
}
void TearDown() override {
auto* floating_workspace_service2 =
FloatingWorkspaceService::GetForProfile(profile2());
if (floating_workspace_service2) {
floating_workspace_service2->ShutDownServicesAndObservers();
}
profile2_ = nullptr;
FloatingWorkspaceServiceV2Test::TearDown();
}
private:
std::unique_ptr<syncer::TestSyncService> test_sync_service2_;
std::unique_ptr<desks_storage::FakeDeskSyncService> fake_desk_sync_service2_;
base::ScopedTempDir temp_dir2_;
AccountId account_id2_;
std::unique_ptr<NotificationDisplayServiceTester> display_service2_;
std::unique_ptr<apps::AppRegistryCache> cache2_;
std::unique_ptr<syncer::FakeDeviceInfoSyncService>
fake_device_info_sync_service2_;
raw_ptr<TestingProfile> profile2_ = nullptr;
};
TEST_F(FloatingWorkspaceServiceMultiUserTest, TwoUserLoggedInAndCaptureStops) {
PopulateAppsCache();
PopulateAppsCache2();
CreateFloatingWorkspaceServiceForTesting(profile());
CreateFloatingWorkspaceServiceForTesting(profile2());
auto* floating_workspace_service =
FloatingWorkspaceService::GetForProfile(profile());
floating_workspace_service->Init(test_sync_service(),
fake_desk_sync_service(),
fake_device_info_sync_service());
auto* floating_workspace_service2 =
FloatingWorkspaceService::GetForProfile(profile2());
floating_workspace_service2->Init(test_sync_service2(),
fake_desk_sync_service2(),
fake_device_info_sync_service2());
test_sync_service()->SetDownloadStatusFor(
{syncer::DataType::WORKSPACE_DESK},
syncer::SyncService::DataTypeDownloadStatus::kUpToDate);
test_sync_service()->FireStateChanged();
test_sync_service2()->SetDownloadStatusFor(
{syncer::DataType::WORKSPACE_DESK},
syncer::SyncService::DataTypeDownloadStatus::kUpToDate);
test_sync_service2()->FireStateChanged();
fake_user_manager()->SwitchActiveUser(account_id());
// Capture a desk template and upload to current account.
const std::string template_name = "floating_workspace_captured_template";
const base::Time creation_time = base::Time::Now();
std::unique_ptr<DeskTemplate> floating_workspace_template =
MakeTestFloatingWorkspaceDeskTemplate(template_name, creation_time);
mock_desks_client()->SetCapturedDeskTemplate(
std::move(floating_workspace_template));
task_environment().FastForwardBy(
ash::features::kFloatingWorkspaceV2PeriodicJobIntervalInSeconds.Get() +
base::Seconds(1));
// Verify that it has been uploaded.
EXPECT_TRUE(floating_workspace_service->GetLatestFloatingWorkspaceTemplate());
EXPECT_EQ(floating_workspace_service->GetLatestFloatingWorkspaceTemplate()
->template_name(),
base::UTF8ToUTF16(template_name));
// Switch accounts and capture a desk template.
const std::string template_name2 = "floating_workspace_captured_template";
const base::Time creation_time2 = base::Time::Now();
std::unique_ptr<DeskTemplate> floating_workspace_template2 =
MakeTestFloatingWorkspaceDeskTemplate(template_name2, creation_time2);
mock_desks_client()->SetCapturedDeskTemplate(
std::move(floating_workspace_template2));
fake_user_manager()->SwitchActiveUser(account_id2());
floating_workspace_service->OnActiveUserSessionChanged(account_id2());
task_environment().RunUntilIdle();
task_environment().FastForwardBy(
ash::features::kFloatingWorkspaceV2PeriodicJobIntervalInSeconds.Get() +
base::Seconds(1));
user_activity_detector()->set_last_activity_time_for_test(
base::TimeTicks::Now());
// Verify that the latest captured template was before the switch.
EXPECT_EQ(floating_workspace_service->GetLatestFloatingWorkspaceTemplate()
->template_name(),
base::UTF8ToUTF16(template_name));
}
TEST_F(FloatingWorkspaceServiceMultiUserTest,
TwoUserLoggedInAndUploadsToCorrectAccount) {
PopulateAppsCache();
PopulateAppsCache2();
fake_user_manager()->SwitchActiveUser(account_id());
CreateFloatingWorkspaceServiceForTesting(profile());
CreateFloatingWorkspaceServiceForTesting(profile2());
auto* floating_workspace_service =
FloatingWorkspaceService::GetForProfile(profile());
floating_workspace_service->Init(test_sync_service(),
fake_desk_sync_service(),
fake_device_info_sync_service());
auto* floating_workspace_service2 =
FloatingWorkspaceService::GetForProfile(profile2());
floating_workspace_service2->Init(test_sync_service2(),
fake_desk_sync_service2(),
fake_device_info_sync_service2());
test_sync_service()->SetDownloadStatusFor(
{syncer::DataType::WORKSPACE_DESK},
syncer::SyncService::DataTypeDownloadStatus::kUpToDate);
test_sync_service()->FireStateChanged();
test_sync_service2()->SetDownloadStatusFor(
{syncer::DataType::WORKSPACE_DESK},
syncer::SyncService::DataTypeDownloadStatus::kUpToDate);
test_sync_service2()->FireStateChanged();
const std::string template_name = "floating_workspace_captured_template";
const base::Time creation_time = base::Time::Now();
std::unique_ptr<DeskTemplate> floating_workspace_template =
MakeTestFloatingWorkspaceDeskTemplate(template_name, creation_time);
mock_desks_client()->SetCapturedDeskTemplate(
std::move(floating_workspace_template));
task_environment().FastForwardBy(
ash::features::kFloatingWorkspaceV2PeriodicJobIntervalInSeconds.Get() +
base::Seconds(1));
user_activity_detector()->set_last_activity_time_for_test(
base::TimeTicks::Now());
EXPECT_TRUE(floating_workspace_service->GetLatestFloatingWorkspaceTemplate());
EXPECT_FALSE(
floating_workspace_service2->GetLatestFloatingWorkspaceTemplate());
}
} // namespace ash::floating_workspace