blob: 082acb90fd6665b5da05ddd07f555d83ab96ac50 [file] [log] [blame]
// Copyright 2021 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/ui/ash/projector/projector_client_impl.h"
#include <memory>
#include "ash/constants/ash_features.h"
#include "ash/constants/ash_pref_names.h"
#include "ash/projector/test/mock_projector_client.h"
#include "ash/public/cpp/projector/projector_client.h"
#include "ash/public/cpp/projector/projector_new_screencast_precondition.h"
#include "ash/webui/projector_app/public/cpp/projector_app_constants.h"
#include "base/bind.h"
#include "base/callback_forward.h"
#include "base/run_loop.h"
#include "base/test/scoped_feature_list.h"
#include "chrome/browser/apps/app_service/app_icon/app_icon_factory.h"
#include "chrome/browser/apps/app_service/app_service_proxy.h"
#include "chrome/browser/apps/app_service/app_service_proxy_factory.h"
#include "chrome/browser/ash/drive/drive_integration_service.h"
#include "chrome/browser/ash/drive/drivefs_test_support.h"
#include "chrome/browser/ash/login/test/fake_gaia_mixin.h"
#include "chrome/browser/ash/login/test/logged_in_user_mixin.h"
#include "chrome/browser/ash/system_web_apps/system_web_app_manager.h"
#include "chrome/browser/ash/system_web_apps/types/system_web_app_type.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/ash/projector/projector_app_client_impl.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_window.h"
#include "chrome/browser/ui/tabs/tab_strip_model.h"
#include "chrome/browser/ui/web_applications/system_web_app_ui_utils.h"
#include "chrome/browser/web_applications/web_app_id.h"
#include "chrome/browser/web_applications/web_app_provider.h"
#include "chrome/test/base/in_process_browser_test.h"
#include "chrome/test/base/mixin_based_in_process_browser_test.h"
#include "chrome/test/base/ui_test_utils.h"
#include "components/account_id/account_id.h"
#include "components/services/app_service/public/cpp/app_registry_cache.h"
#include "components/services/app_service/public/cpp/app_types.h"
#include "components/services/app_service/public/cpp/icon_types.h"
#include "content/public/browser/navigation_entry.h"
#include "content/public/browser/web_contents.h"
#include "content/public/common/page_type.h"
#include "content/public/test/browser_test.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "url/gurl.h"
namespace ash {
namespace {
apps::AppServiceProxy* GetAppServiceProxy(Profile* profile) {
return apps::AppServiceProxyFactory::GetForProfile(profile);
}
// Returns the account id for logging in.
absl::optional<AccountId> GetPrimaryAccountId(bool is_managed) {
if (is_managed) {
return AccountId::FromUserEmailGaiaId(
ash::FakeGaiaMixin::kEnterpriseUser1,
ash::FakeGaiaMixin::kEnterpriseUser1GaiaId);
}
// Use the default FakeGaiaMixin::kFakeUserEmail consumer test account id.
return absl::nullopt;
}
} // namespace
// A class helps to verify enable/disable Drive could invoke
// ProjectorAppClient::Observer::OnDriveFsMountStatusChanged().
class DriveFsMountStatusWaiter : public ProjectorAppClient::Observer {
public:
explicit DriveFsMountStatusWaiter(drive::DriveIntegrationService* service)
: service_(service) {
GetProjectorAppClientImpl()->AddObserver(this);
}
DriveFsMountStatusWaiter(const DriveFsMountStatusWaiter&) = delete;
DriveFsMountStatusWaiter& operator=(const DriveFsMountStatusWaiter&) = delete;
~DriveFsMountStatusWaiter() override {
GetProjectorAppClientImpl()->RemoveObserver(this);
}
// ProjectorAppClient::Observer:
void OnNewScreencastPreconditionChanged(
const NewScreencastPrecondition& condition) override {
std::move(quit_closure_).Run();
}
MOCK_METHOD1(OnScreencastsPendingStatusChanged,
void(const PendingScreencastSet&));
MOCK_METHOD1(OnSodaProgress, void(int));
MOCK_METHOD0(OnSodaError, void());
MOCK_METHOD0(OnSodaInstalled, void());
void SetDriveEnabled(bool enabled_drive, base::OnceClosure quit_closure) {
quit_closure_ = std::move(quit_closure);
service_->SetEnabled(enabled_drive);
}
ProjectorAppClientImpl* GetProjectorAppClientImpl() {
return static_cast<ProjectorAppClientImpl*>(ash::ProjectorAppClient::Get());
}
private:
base::OnceClosure quit_closure_;
drive::DriveIntegrationService* service_;
};
class ProjectorClientTest : public InProcessBrowserTest {
public:
ProjectorClientTest() {
scoped_feature_list_.InitWithFeatures(
{features::kProjector, features::kProjectorAnnotator,
features::kOnDeviceSpeechRecognition},
{});
}
~ProjectorClientTest() override = default;
ProjectorClientTest(const ProjectorClientTest&) = delete;
ProjectorClientTest& operator=(const ProjectorClientTest&) = delete;
// InProcessBrowserTest:
void SetUpInProcessBrowserTestFixture() override {
create_drive_integration_service_ =
base::BindRepeating(&ProjectorClientTest::CreateDriveIntegrationService,
base::Unretained(this));
service_factory_for_test_ = std::make_unique<
drive::DriveIntegrationServiceFactory::ScopedFactoryForTest>(
&create_drive_integration_service_);
}
// This test helper verifies that navigating to the |url| doesn't result in a
// 404 error.
void VerifyUrlValid(const char* url) {
GURL gurl(url);
EXPECT_TRUE(gurl.is_valid()) << "url isn't valid: " << url;
EXPECT_TRUE(ui_test_utils::NavigateToURL(browser(), gurl))
<< "navigating to url failed: " << url;
content::WebContents* tab =
browser()->tab_strip_model()->GetActiveWebContents();
EXPECT_EQ(tab->GetController().GetLastCommittedEntry()->GetPageType(),
content::PAGE_TYPE_NORMAL)
<< "page has unexpected errors: " << url;
}
drive::DriveIntegrationService* CreateDriveIntegrationService(
Profile* profile) {
base::FilePath mount_path = profile->GetPath().Append("drivefs");
fake_drivefs_helpers_[profile] =
std::make_unique<drive::FakeDriveFsHelper>(profile, mount_path);
// The integration service is owned by `KeyedServiceFactory`.
auto* integration_service = new drive::DriveIntegrationService(
profile, /*test_mount_point_name=*/std::string(), mount_path,
fake_drivefs_helpers_[profile]->CreateFakeDriveFsListenerFactory());
return integration_service;
}
ProjectorClient* client() { return ProjectorClient::Get(); }
private:
drive::DriveIntegrationServiceFactory::FactoryCallback
create_drive_integration_service_;
std::unique_ptr<drive::DriveIntegrationServiceFactory::ScopedFactoryForTest>
service_factory_for_test_;
std::map<Profile*, std::unique_ptr<drive::FakeDriveFsHelper>>
fake_drivefs_helpers_;
base::test::ScopedFeatureList scoped_feature_list_;
};
// This test verifies that the (un)trusted Projector app and annotator WebUI
// URLs are valid.
IN_PROC_BROWSER_TEST_F(ProjectorClientTest, AppUrlsValid) {
VerifyUrlValid(kChromeUITrustedProjectorAppUrl);
VerifyUrlValid(kChromeUIUntrustedProjectorAppUrl);
VerifyUrlValid(kChromeUITrustedAnnotatorAppUrl);
VerifyUrlValid(kChromeUIUntrustedAnnotatorAppUrl);
}
IN_PROC_BROWSER_TEST_F(ProjectorClientTest, OpenProjectorApp) {
auto* profile = browser()->profile();
ash::SystemWebAppManager::GetForTest(profile)->InstallSystemAppsForTesting();
client()->OpenProjectorApp();
web_app::FlushSystemWebAppLaunchesForTesting(profile);
// Verify that Projector App is opened.
Browser* app_browser = web_app::FindSystemWebAppBrowser(
profile, ash::SystemWebAppType::PROJECTOR);
ASSERT_TRUE(app_browser);
content::WebContents* tab =
app_browser->tab_strip_model()->GetActiveWebContents();
ASSERT_TRUE(tab);
EXPECT_EQ(tab->GetController().GetVisibleEntry()->GetPageType(),
content::PAGE_TYPE_NORMAL);
}
IN_PROC_BROWSER_TEST_F(ProjectorClientTest, MinimizeProjectorApp) {
auto* profile = browser()->profile();
ash::SystemWebAppManager::GetForTest(profile)->InstallSystemAppsForTesting();
client()->OpenProjectorApp();
web_app::FlushSystemWebAppLaunchesForTesting(profile);
// Verify that Projector App is opened.
Browser* app_browser = web_app::FindSystemWebAppBrowser(
profile, ash::SystemWebAppType::PROJECTOR);
ASSERT_TRUE(app_browser);
content::WebContents* tab =
app_browser->tab_strip_model()->GetActiveWebContents();
ASSERT_TRUE(tab);
EXPECT_EQ(tab->GetController().GetVisibleEntry()->GetPageType(),
content::PAGE_TYPE_NORMAL);
client()->MinimizeProjectorApp();
// Verify that Projector App is minimized.
EXPECT_TRUE(app_browser->window()->IsMinimized());
}
IN_PROC_BROWSER_TEST_F(ProjectorClientTest, CloseProjectorApp) {
auto* profile = browser()->profile();
ash::SystemWebAppManager::GetForTest(profile)->InstallSystemAppsForTesting();
client()->OpenProjectorApp();
web_app::FlushSystemWebAppLaunchesForTesting(profile);
// Verify that Projector App is opened.
Browser* app_browser = web_app::FindSystemWebAppBrowser(
profile, ash::SystemWebAppType::PROJECTOR);
ASSERT_TRUE(app_browser);
content::WebContents* tab =
app_browser->tab_strip_model()->GetActiveWebContents();
ASSERT_TRUE(tab);
EXPECT_EQ(tab->GetController().GetVisibleEntry()->GetPageType(),
content::PAGE_TYPE_NORMAL);
EXPECT_FALSE(app_browser->IsAttemptingToCloseBrowser());
client()->CloseProjectorApp();
// Verify that Projector App is closing.
EXPECT_TRUE(app_browser->IsAttemptingToCloseBrowser());
}
IN_PROC_BROWSER_TEST_F(ProjectorClientTest, GetDriveFsMountPointPath) {
ASSERT_TRUE(client()->IsDriveFsMounted());
ASSERT_FALSE(client()->IsDriveFsMountFailed());
base::FilePath mounted_path;
ASSERT_TRUE(client()->GetDriveFsMountPointPath(&mounted_path));
ASSERT_EQ(browser()->profile()->GetPath().Append("drivefs"), mounted_path);
}
IN_PROC_BROWSER_TEST_F(ProjectorClientTest, DriveUnmountedAndRemounted) {
drive::DriveIntegrationService* service =
drive::DriveIntegrationServiceFactory::FindForProfile(
browser()->profile());
EXPECT_TRUE(service->is_enabled());
DriveFsMountStatusWaiter observer{service};
{
base::RunLoop run_loop;
observer.SetDriveEnabled(
/*enabled_drive=*/false, run_loop.QuitClosure());
run_loop.Run();
}
{
base::RunLoop run_loop;
observer.SetDriveEnabled(
/*enabled_drive=*/true, run_loop.QuitClosure());
run_loop.Run();
}
}
// Tests Projector client for child and managed users.
class ProjectorClientManagedTest
: public MixinBasedInProcessBrowserTest,
public testing::WithParamInterface</*IsChild=*/bool> {
protected:
void SetUpOnMainThread() override {
MixinBasedInProcessBrowserTest::SetUpOnMainThread();
logged_in_user_mixin_.LogInUser();
}
bool is_child() const { return GetParam(); }
bool is_managed() const { return !is_child(); }
ProjectorClient* client() { return ProjectorClient::Get(); }
std::string GetPolicy() {
if (is_child())
return ash::prefs::kProjectorDogfoodForFamilyLinkEnabled;
return ash::prefs::kProjectorAllowByPolicy;
}
apps::Readiness GetAppReadiness(const web_app::AppId& app_id) {
apps::Readiness readiness;
bool app_found =
GetAppServiceProxy(browser()->profile())
->AppRegistryCache()
.ForOneApp(app_id, [&readiness](const apps::AppUpdate& update) {
readiness = update.Readiness();
});
EXPECT_TRUE(app_found);
return readiness;
}
absl::optional<apps::IconKey> GetAppIconKey(const web_app::AppId& app_id) {
absl::optional<apps::IconKey> icon_key;
bool app_found =
GetAppServiceProxy(browser()->profile())
->AppRegistryCache()
.ForOneApp(app_id, [&icon_key](const apps::AppUpdate& update) {
icon_key = update.IconKey();
});
EXPECT_TRUE(app_found);
return icon_key;
}
private:
LoggedInUserMixin logged_in_user_mixin_{
&mixin_host_,
is_child() ? LoggedInUserMixin::LogInType::kChild
: LoggedInUserMixin::LogInType::kRegular,
embedded_test_server(),
this,
/*should_launch_browser=*/true,
GetPrimaryAccountId(is_managed())};
};
IN_PROC_BROWSER_TEST_P(ProjectorClientManagedTest,
CantOpenProjectorAppWithoutPolicy) {
auto* profile = browser()->profile();
ash::SystemWebAppManager::GetForTest(profile)->InstallSystemAppsForTesting();
client()->OpenProjectorApp();
web_app::FlushSystemWebAppLaunchesForTesting(profile);
// Verify that Projector App is not opened.
Browser* app_browser = web_app::FindSystemWebAppBrowser(
profile, ash::SystemWebAppType::PROJECTOR);
EXPECT_FALSE(app_browser);
}
// Prevents a regression to b/230779397.
IN_PROC_BROWSER_TEST_P(ProjectorClientManagedTest, DisableThenEnablePolicy) {
auto* profile = browser()->profile();
profile->GetPrefs()->SetBoolean(GetPolicy(), true);
ash::SystemWebAppManager::GetForTest(profile)->InstallSystemAppsForTesting();
client()->OpenProjectorApp();
web_app::FlushSystemWebAppLaunchesForTesting(profile);
// Verify the user can open the Projector App when the policy is enabled.
Browser* app_browser = web_app::FindSystemWebAppBrowser(
profile, ash::SystemWebAppType::PROJECTOR);
ASSERT_TRUE(app_browser);
content::WebContents* tab =
app_browser->tab_strip_model()->GetActiveWebContents();
ASSERT_TRUE(tab);
EXPECT_EQ(tab->GetController().GetVisibleEntry()->GetPageType(),
content::PAGE_TYPE_NORMAL);
// Suppose the policy flips to false while the user is still signed in and has
// the Projector app open.
profile->GetPrefs()->SetBoolean(GetPolicy(), false);
// The Projector app immediately closes to prevent further access.
EXPECT_TRUE(app_browser->IsAttemptingToCloseBrowser());
// We can't uninstall the Projector SWA until the next session, but the icon
// is greyed out and disabled.
EXPECT_EQ(apps::Readiness::kDisabledByPolicy,
GetAppReadiness(kChromeUITrustedProjectorSwaAppId));
EXPECT_TRUE(apps::IconEffects::kBlocked &
GetAppIconKey(kChromeUITrustedProjectorSwaAppId)->icon_effects);
// The app can re-enable too if it's already installed and the policy flips to
// true.
profile->GetPrefs()->SetBoolean(GetPolicy(), true);
EXPECT_EQ(apps::Readiness::kReady,
GetAppReadiness(kChromeUITrustedProjectorSwaAppId));
EXPECT_FALSE(apps::IconEffects::kBlocked &
GetAppIconKey(kChromeUITrustedProjectorSwaAppId)->icon_effects);
}
INSTANTIATE_TEST_SUITE_P(,
ProjectorClientManagedTest,
/*IsChild=*/testing::Bool());
} // namespace ash