| // Copyright 2023 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/video_conference/video_conference_app_service_client.h" |
| |
| #include <cstdlib> |
| #include <utility> |
| #include <vector> |
| |
| #include "ash/constants/ash_features.h" |
| #include "ash/constants/ash_switches.h" |
| #include "ash/session/session_controller_impl.h" |
| #include "ash/shell.h" |
| #include "ash/system/video_conference/fake_video_conference_tray_controller.h" |
| #include "ash/system/video_conference/video_conference_common.h" |
| #include "ash/test/test_window_builder.h" |
| #include "base/command_line.h" |
| #include "base/memory/raw_ptr.h" |
| #include "base/run_loop.h" |
| #include "base/strings/strcat.h" |
| #include "base/strings/string_number_conversions.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "base/test/bind.h" |
| #include "base/test/scoped_feature_list.h" |
| #include "base/unguessable_token.h" |
| #include "chrome/browser/apps/app_service/app_service_proxy_ash.h" |
| #include "chrome/browser/apps/app_service/app_service_proxy_factory.h" |
| #include "chrome/browser/ash/crosapi/crosapi_ash.h" |
| #include "chrome/browser/ash/crosapi/crosapi_manager.h" |
| #include "chrome/browser/ash/video_conference/video_conference_manager_ash.h" |
| #include "chrome/browser/chromeos/video_conference/video_conference_manager_client_common.h" |
| #include "chrome/browser/profiles/profile_manager.h" |
| #include "chrome/browser/ui/browser.h" |
| #include "chrome/test/base/in_process_browser_test.h" |
| #include "chromeos/crosapi/mojom/video_conference.mojom.h" |
| #include "components/services/app_service/public/cpp/app_capability_access_cache_wrapper.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/capability_access_update.h" |
| #include "components/services/app_service/public/cpp/instance_registry.h" |
| #include "components/user_manager/user_manager.h" |
| #include "content/public/test/browser_test.h" |
| #include "content/public/test/test_utils.h" |
| |
| namespace ash { |
| namespace { |
| |
| using AppIdString = std::string; |
| |
| constexpr char kAppId1[] = "random_app_id_1"; |
| constexpr char kAppName1[] = "random_app_name_1"; |
| constexpr char kAppId2[] = "random_app_id_2"; |
| constexpr char kAppName2[] = "random_app_name_2"; |
| |
| // Creates an app with given id and permissions. |
| apps::AppPtr MakeApp(const AppIdString& app_id, |
| bool has_camera_permission, |
| bool has_microphone_permission, |
| apps::AppType app_type) { |
| apps::AppPtr app = std::make_unique<apps::App>(app_type, app_id); |
| if (app_id == kAppId1) { |
| app->name = kAppName1; |
| } |
| if (app_id == kAppId2) { |
| app->name = kAppName2; |
| } |
| if (base::Contains(::video_conference::kSkipAppIds, app_id)) { |
| app->name = base::StrCat({"AppName-", app_id}); |
| } |
| |
| app->publisher_id = app_id; |
| |
| app->permissions.push_back(std::make_unique<apps::Permission>( |
| apps::PermissionType::kCamera, |
| std::make_unique<apps::PermissionValue>(has_camera_permission), |
| /*is_managed=*/false)); |
| app->permissions.push_back(std::make_unique<apps::Permission>( |
| apps::PermissionType::kMicrophone, |
| std::make_unique<apps::PermissionValue>(has_microphone_permission), |
| /*is_managed=*/false)); |
| return app; |
| } |
| |
| // This fake instance class simulates the creation, destruction, hiding and |
| // showing an app instance. |
| class FakeAppInstance { |
| public: |
| FakeAppInstance(apps::InstanceRegistry* instance_registry, |
| const AppIdString& app_id) { |
| instance_registry_ = instance_registry; |
| window_ = TestWindowBuilder().Build(); |
| instance_ = std::make_unique<apps::Instance>( |
| app_id, base::UnguessableToken::Create(), window_.get()); |
| } |
| |
| aura::Window* window() { return window_.get(); } |
| |
| void Start() { |
| auto instance = instance_->Clone(); |
| instance->UpdateState(apps::InstanceState::kStarted, base::Time::Now()); |
| instance_registry_->OnInstance(std::move(instance)); |
| } |
| |
| void Close() { |
| auto instance = instance_->Clone(); |
| instance->UpdateState(apps::InstanceState::kDestroyed, base::Time::Now()); |
| instance_registry_->OnInstance(std::move(instance)); |
| } |
| |
| void Show() { |
| window_->Show(); |
| // Ideally, the following should be automatically triggered by showing the |
| // window_; but that is not the case for now. |
| auto instance = instance_->Clone(); |
| instance->UpdateState(apps::InstanceState::kActive, base::Time::Now()); |
| instance_registry_->OnInstance(std::move(instance)); |
| } |
| |
| void Hide() { |
| window_->Hide(); |
| // Ideally, the following should be automatically triggered by hiding the |
| // window_; but that is not the case for now. |
| auto instance = instance_->Clone(); |
| instance->UpdateState(apps::InstanceState::kHidden, base::Time::Now()); |
| instance_registry_->OnInstance(std::move(instance)); |
| } |
| |
| private: |
| std::unique_ptr<aura::Window> window_; |
| std::unique_ptr<apps::Instance> instance_; |
| base::raw_ptr<apps::InstanceRegistry> instance_registry_; |
| }; |
| |
| } // namespace |
| |
| class VideoConferenceAppServiceClientTest : public InProcessBrowserTest { |
| public: |
| void SetUpCommandLine(base::CommandLine* command_line) override { |
| command_line->AppendSwitch( |
| ::ash::switches::kCameraEffectsSupportedByHardware); |
| } |
| |
| void SetUpOnMainThread() override { |
| InProcessBrowserTest::SetUpOnMainThread(); |
| |
| client_ = VideoConferenceAppServiceClient::GetForTesting(); |
| |
| Profile* profile = ProfileManager::GetActiveUserProfile(); |
| instance_registry_ = &apps::AppServiceProxyFactory::GetForProfile(profile) |
| ->InstanceRegistry(); |
| app_registry_cache_ = &apps::AppServiceProxyFactory::GetForProfile(profile) |
| ->AppRegistryCache(); |
| capability_cache_ = |
| apps::AppCapabilityAccessCacheWrapper::Get() |
| .GetAppCapabilityAccessCache(user_manager::UserManager::Get() |
| ->GetActiveUser() |
| ->GetAccountId()); |
| } |
| |
| // This function creates an app with given id and name, and adds the app into |
| // AppRegistryCache of current profile. |
| void InstallApp(const std::string& app_id, |
| apps::AppType app_type = apps::AppType::kArc) { |
| std::vector<apps::AppPtr> deltas; |
| deltas.push_back(MakeApp(app_id, /*has_camera_permission=*/false, |
| /*has_microphone_permission=*/false, app_type)); |
| app_registry_cache_->OnApps(std::move(deltas), apps::AppType::kUnknown, |
| /*should_notify_initialized=*/false); |
| } |
| |
| // Update the permission of current `app_id`. |
| void UpdateAppPermision(const std::string& app_id, |
| bool has_camera_permission, |
| bool has_microphone_permission) { |
| std::vector<apps::AppPtr> deltas; |
| deltas.push_back(MakeApp(app_id, has_camera_permission, |
| has_microphone_permission, GetAppType(app_id))); |
| app_registry_cache_->OnApps(std::move(deltas), apps::AppType::kUnknown, |
| /*should_notify_initialized=*/false); |
| } |
| |
| // Set the camera/michrophone accessing info for app with `app_id`. |
| void SetAppCapabilityAccess(const AppIdString& app_id, |
| bool is_capturing_camera, |
| bool is_capturing_microphone) { |
| auto delta = std::make_unique<apps::CapabilityAccess>(app_id); |
| delta->camera = is_capturing_camera; |
| delta->microphone = is_capturing_microphone; |
| |
| std::vector<apps::CapabilityAccessPtr> deltas; |
| deltas.push_back(std::move(delta)); |
| |
| capability_cache_->OnCapabilityAccesses(std::move(deltas)); |
| } |
| |
| // Adds {id, state} pair to client_->id_to_app_state_. |
| void AddAppState(const AppIdString& app_id, |
| const VideoConferenceAppServiceClient::AppState& state) { |
| (client_->id_to_app_state_)[app_id] = state; |
| } |
| |
| std::string GetAppName(const AppIdString& app_id) { |
| return client_->GetAppName(app_id); |
| } |
| |
| apps::AppType GetAppType(const AppIdString& app_id) { |
| return client_->GetAppType(app_id); |
| } |
| |
| VideoConferenceAppServiceClient::VideoConferencePermissions GetAppPermission( |
| const AppIdString& app_id) { |
| return client_->GetAppPermission(app_id); |
| } |
| |
| std::vector<crosapi::mojom::VideoConferenceMediaAppInfoPtr> GetMediaApps() { |
| std::vector<crosapi::mojom::VideoConferenceMediaAppInfoPtr> media_app_info; |
| |
| client_->GetMediaApps(base::BindLambdaForTesting( |
| [&media_app_info]( |
| std::vector<crosapi::mojom::VideoConferenceMediaAppInfoPtr> |
| result) { media_app_info = std::move(result); })); |
| |
| return media_app_info; |
| } |
| |
| // Returns current VideoConferenceMediaState in the VideoConferenceManagerAsh |
| VideoConferenceMediaState GetMediaStateInVideoConferenceManagerAsh() { |
| return crosapi::CrosapiManager::Get() |
| ->crosapi_ash() |
| ->video_conference_manager_ash() |
| ->GetAggregatedState(); |
| } |
| |
| protected: |
| raw_ptr<apps::InstanceRegistry, ExperimentalAsh> instance_registry_ = nullptr; |
| raw_ptr<apps::AppRegistryCache, ExperimentalAsh> app_registry_cache_ = |
| nullptr; |
| raw_ptr<apps::AppCapabilityAccessCache, ExperimentalAsh> capability_cache_ = |
| nullptr; |
| raw_ptr<VideoConferenceAppServiceClient, ExperimentalAsh> client_ = nullptr; |
| |
| base::test::ScopedFeatureList scoped_feature_list_{ |
| ash::features::kVideoConference}; |
| }; |
| |
| IN_PROC_BROWSER_TEST_F(VideoConferenceAppServiceClientTest, GetAppName) { |
| // AppName should be empty if it is not installed. |
| EXPECT_EQ(GetAppName(kAppId1), std::string()); |
| |
| InstallApp(kAppId1); |
| |
| // AppName should be correct if installed. |
| EXPECT_EQ(GetAppName(kAppId1), kAppName1); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(VideoConferenceAppServiceClientTest, GetAppType) { |
| // AppType should be kUnknown if it is not installed. |
| EXPECT_EQ(GetAppType(kAppId1), apps::AppType::kUnknown); |
| |
| InstallApp(kAppId1); |
| |
| // AppType for the test app installed should be Arc. |
| EXPECT_EQ(GetAppType(kAppId1), apps::AppType::kArc); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(VideoConferenceAppServiceClientTest, GetAppPermission) { |
| InstallApp(kAppId1); |
| |
| VideoConferenceAppServiceClient::VideoConferencePermissions permission = |
| GetAppPermission(kAppId1); |
| EXPECT_FALSE(permission.has_camera_permission); |
| EXPECT_FALSE(permission.has_microphone_permission); |
| |
| UpdateAppPermision(kAppId1, /*has_camera_permission=*/false, |
| /*has_microphone_permission=*/true); |
| permission = GetAppPermission(kAppId1); |
| EXPECT_FALSE(permission.has_camera_permission); |
| EXPECT_TRUE(permission.has_microphone_permission); |
| |
| UpdateAppPermision(kAppId1, /*has_camera_permission=*/true, |
| /*has_microphone_permission=*/true); |
| permission = GetAppPermission(kAppId1); |
| EXPECT_TRUE(permission.has_camera_permission); |
| EXPECT_TRUE(permission.has_microphone_permission); |
| |
| UpdateAppPermision(kAppId1, /*has_camera_permission=*/true, |
| /*has_microphone_permission=*/false); |
| permission = GetAppPermission(kAppId1); |
| EXPECT_TRUE(permission.has_camera_permission); |
| EXPECT_FALSE(permission.has_microphone_permission); |
| |
| UpdateAppPermision(kAppId1, /*has_camera_permission=*/false, |
| /*has_microphone_permission=*/false); |
| permission = GetAppPermission(kAppId1); |
| EXPECT_FALSE(permission.has_camera_permission); |
| EXPECT_FALSE(permission.has_microphone_permission); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(VideoConferenceAppServiceClientTest, GetMediaApps) { |
| // Add {kAppId1, state1} pair to the client_. |
| const base::UnguessableToken token1 = base::UnguessableToken::Create(); |
| const VideoConferenceAppServiceClient::AppState state1{ |
| token1, base::Time::Now(), true, true}; |
| AddAppState(kAppId1, state1); |
| |
| // Add {kAppId2, state2} pair to the client_. |
| const base::UnguessableToken token2 = base::UnguessableToken::Create(); |
| const VideoConferenceAppServiceClient::AppState state2{ |
| token2, base::Time::Now(), true, false}; |
| AddAppState(kAppId2, state2); |
| |
| std::vector<crosapi::mojom::VideoConferenceMediaAppInfoPtr> media_app_info = |
| GetMediaApps(); |
| |
| // GetMediaApps will not return anything because unrecognized apps will be |
| // skipped. |
| EXPECT_TRUE(media_app_info.empty()); |
| |
| InstallApp(kAppId1); |
| |
| // GetMediaApps should return kAppId1 since it is installed. |
| media_app_info = GetMediaApps(); |
| ASSERT_EQ(media_app_info.size(), 1u); |
| |
| crosapi::mojom::VideoConferenceMediaAppInfoPtr expected_media_app_info = |
| crosapi::mojom::VideoConferenceMediaAppInfo::New( |
| /*id=*/token1, |
| /*last_activity_time=*/state1.last_activity_time, |
| /*is_capturing_camera=*/state1.is_capturing_camera, |
| /*is_capturing_microphone=*/state1.is_capturing_microphone, |
| /*is_capturing_screen=*/false, |
| /*title=*/base::UTF8ToUTF16(std::string(kAppName1)), |
| /*url=*/absl::nullopt, |
| /*app_type=*/crosapi::mojom::VideoConferenceAppType::kArcApp); |
| |
| EXPECT_TRUE(media_app_info[0].Equals(expected_media_app_info)); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(VideoConferenceAppServiceClientTest, ReturnToApp) { |
| // Add two instance for kAppId1. |
| FakeAppInstance instance1(instance_registry_, kAppId1); |
| instance1.Start(); |
| FakeAppInstance instance2(instance_registry_, kAppId1); |
| instance2.Start(); |
| |
| aura::Window* window1 = instance1.window(); |
| aura::Window* window2 = instance2.window(); |
| window1->Hide(); |
| window2->Hide(); |
| |
| const base::UnguessableToken token1 = base::UnguessableToken::Create(); |
| bool reactivated_app = false; |
| |
| // Return to token1 should not do anything since the token1 is not in the |
| // client_->id_to_app_state_. |
| client_->ReturnToApp( |
| token1, base::BindLambdaForTesting([&reactivated_app](bool result) { |
| reactivated_app = result; |
| })); |
| |
| EXPECT_FALSE(reactivated_app); |
| EXPECT_FALSE(window1->IsVisible()); |
| EXPECT_FALSE(window2->IsVisible()); |
| |
| // Add pair {token1, state1} to client_->id_to_app_state_. |
| const VideoConferenceAppServiceClient::AppState state1{ |
| token1, base::Time::Now(), true, true}; |
| AddAppState(kAppId1, state1); |
| |
| // Return to token1 should show all instances associated with kAppId1. |
| client_->ReturnToApp( |
| token1, base::BindLambdaForTesting([&reactivated_app](bool result) { |
| reactivated_app = result; |
| })); |
| |
| EXPECT_TRUE(reactivated_app); |
| EXPECT_TRUE(window1->IsVisible()); |
| EXPECT_TRUE(window2->IsVisible()); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(VideoConferenceAppServiceClientTest, MediaCapturing) { |
| // Install two apps so that they will can be tracked inside GetMediaApps. |
| InstallApp(kAppId1); |
| InstallApp(kAppId2); |
| FakeAppInstance instance1(instance_registry_, kAppId1); |
| instance1.Start(); |
| FakeAppInstance instance2(instance_registry_, kAppId2); |
| instance2.Start(); |
| |
| // no-camera, no-mic should not start a tracking of the app. |
| SetAppCapabilityAccess(kAppId1, /*is_capturing_camera=*/false, |
| /*is_capturing_microphone=*/false); |
| EXPECT_TRUE(GetMediaApps().empty()); |
| |
| std::vector<crosapi::mojom::VideoConferenceMediaAppInfoPtr> media_app_info; |
| |
| // has-camera, no-mic should start the tracking of the app. |
| SetAppCapabilityAccess(kAppId1, /*is_capturing_camera=*/true, |
| /*is_capturing_microphone=*/false); |
| media_app_info = GetMediaApps(); |
| crosapi::mojom::VideoConferenceMediaAppInfoPtr expected_media_app_info = |
| crosapi::mojom::VideoConferenceMediaAppInfo::New( |
| /*id=*/media_app_info[0]->id, |
| /*last_activity_time=*/media_app_info[0]->last_activity_time, |
| /*is_capturing_camera=*/true, |
| /*is_capturing_microphone=*/false, |
| /*is_capturing_screen=*/false, |
| /*title=*/media_app_info[0]->title, /*url=*/absl::nullopt, |
| /*app_type=*/crosapi::mojom::VideoConferenceAppType::kArcApp); |
| ASSERT_EQ(media_app_info.size(), 1u); |
| EXPECT_TRUE(media_app_info[0].Equals(expected_media_app_info)); |
| |
| // has-camera, has-mic should change the value of GetMediaApps. |
| SetAppCapabilityAccess(kAppId1, /*is_capturing_camera=*/true, |
| /*is_capturing_microphone=*/true); |
| media_app_info = GetMediaApps(); |
| ASSERT_EQ(media_app_info.size(), 1u); |
| expected_media_app_info->is_capturing_microphone = true; |
| EXPECT_TRUE(media_app_info[0].Equals(expected_media_app_info)); |
| |
| // no-camera, has-mic should change the value of GetMediaApps. |
| SetAppCapabilityAccess(kAppId1, /*is_capturing_camera=*/false, |
| /*is_capturing_microphone=*/true); |
| media_app_info = GetMediaApps(); |
| ASSERT_EQ(media_app_info.size(), 1u); |
| expected_media_app_info->is_capturing_camera = false; |
| EXPECT_TRUE(media_app_info[0].Equals(expected_media_app_info)); |
| |
| // no-camera, no-mic should change the value of GetMediaApps; but not removing |
| // the tracking app. |
| SetAppCapabilityAccess(kAppId1, /*is_capturing_camera=*/false, |
| /*is_capturing_microphone=*/false); |
| media_app_info = GetMediaApps(); |
| ASSERT_EQ(media_app_info.size(), 1u); |
| expected_media_app_info->is_capturing_microphone = false; |
| EXPECT_TRUE(media_app_info[0].Equals(expected_media_app_info)); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(VideoConferenceAppServiceClientTest, LastActivityTime) { |
| // Start an instance of kAppId1. |
| InstallApp(kAppId1); |
| FakeAppInstance instance1(instance_registry_, kAppId1); |
| instance1.Start(); |
| |
| // has-camera, has-mic should start tracking of the kAppId1. |
| SetAppCapabilityAccess(kAppId1, /*is_capturing_camera=*/true, |
| /*is_capturing_microphone=*/true); |
| |
| std::vector<crosapi::mojom::VideoConferenceMediaAppInfoPtr> media_app_info; |
| |
| media_app_info = GetMediaApps(); |
| crosapi::mojom::VideoConferenceMediaAppInfoPtr expected_media_app_info = |
| crosapi::mojom::VideoConferenceMediaAppInfo::New( |
| /*id=*/media_app_info[0]->id, |
| /*last_activity_time=*/media_app_info[0]->last_activity_time, |
| /*is_capturing_camera=*/true, |
| /*is_capturing_microphone=*/true, |
| /*is_capturing_screen=*/false, |
| /*title=*/media_app_info[0]->title, /*url=*/absl::nullopt, |
| /*app_type=*/crosapi::mojom::VideoConferenceAppType::kArcApp); |
| ASSERT_EQ(media_app_info.size(), 1u); |
| EXPECT_TRUE(media_app_info[0].Equals(expected_media_app_info)); |
| |
| // Hide should not update last activity time. |
| instance1.Hide(); |
| media_app_info = GetMediaApps(); |
| ASSERT_EQ(media_app_info.size(), 1u); |
| EXPECT_TRUE(media_app_info[0].Equals(expected_media_app_info)); |
| |
| // Show should update last activity time. |
| instance1.Show(); |
| media_app_info = GetMediaApps(); |
| ASSERT_EQ(media_app_info.size(), 1u); |
| EXPECT_GT(media_app_info[0]->last_activity_time, |
| expected_media_app_info->last_activity_time); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(VideoConferenceAppServiceClientTest, CloseApp) { |
| // Start two instance of kAppId1. |
| InstallApp(kAppId1); |
| FakeAppInstance instance1(instance_registry_, kAppId1); |
| instance1.Start(); |
| FakeAppInstance instance2(instance_registry_, kAppId1); |
| instance2.Start(); |
| |
| // No media app should be recorded till now. |
| EXPECT_TRUE(GetMediaApps().empty()); |
| |
| // has-camera, has-mic should start a tracking of the app. |
| SetAppCapabilityAccess(kAppId1, /*is_capturing_camera=*/true, |
| /*is_capturing_microphone=*/true); |
| |
| std::vector<crosapi::mojom::VideoConferenceMediaAppInfoPtr> media_app_info; |
| |
| media_app_info = GetMediaApps(); |
| crosapi::mojom::VideoConferenceMediaAppInfoPtr expected_media_app_info = |
| crosapi::mojom::VideoConferenceMediaAppInfo::New( |
| /*id=*/media_app_info[0]->id, |
| /*last_activity_time=*/media_app_info[0]->last_activity_time, |
| /*is_capturing_camera=*/true, |
| /*is_capturing_microphone=*/true, |
| /*is_capturing_screen=*/false, |
| /*title=*/media_app_info[0]->title, /*url=*/absl::nullopt, |
| /*app_type=*/crosapi::mojom::VideoConferenceAppType::kArcApp); |
| ASSERT_EQ(media_app_info.size(), 1u); |
| EXPECT_TRUE(media_app_info[0].Equals(expected_media_app_info)); |
| |
| // Closing instance1 should not remove tracking of kAppId1. |
| instance1.Close(); |
| // Wait for the VideoConferenceAppServiceClient::MaybeRemoveApp to be called |
| // in the PostTask. |
| base::RunLoop().RunUntilIdle(); |
| media_app_info = GetMediaApps(); |
| ASSERT_EQ(media_app_info.size(), 1u); |
| EXPECT_TRUE(media_app_info[0].Equals(expected_media_app_info)); |
| |
| // Closing instance2 should remove trackingg of kAppId1. |
| instance2.Close(); |
| // Wait for the VideoConferenceAppServiceClient::MaybeRemoveApp to be called |
| // in the PostTask. |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_TRUE(GetMediaApps().empty()); |
| |
| // This should not add the app tracking back because there is no running |
| // instance. |
| SetAppCapabilityAccess(kAppId1, /*is_capturing_camera=*/false, |
| /*is_capturing_microphone=*/true); |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_TRUE(GetMediaApps().empty()); |
| |
| // This should not add the app tracking back because there is no running |
| // instance. |
| SetAppCapabilityAccess(kAppId1, /*is_capturing_camera=*/false, |
| /*is_capturing_microphone=*/false); |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_TRUE(GetMediaApps().empty()); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(VideoConferenceAppServiceClientTest, |
| HandleMediaUsageUpdate) { |
| // Install two apps with permissions. |
| InstallApp(kAppId1); |
| InstallApp(kAppId2); |
| UpdateAppPermision(kAppId1, /*has_camera_permission=*/true, |
| /*has_microphone_permission=*/false); |
| UpdateAppPermision(kAppId2, /*has_camera_permission=*/false, |
| /*has_microphone_permission=*/true); |
| |
| // Start two running instance. |
| FakeAppInstance instance1(instance_registry_, kAppId1); |
| instance1.Start(); |
| FakeAppInstance instance2(instance_registry_, kAppId2); |
| instance2.Start(); |
| |
| VideoConferenceMediaState state = GetMediaStateInVideoConferenceManagerAsh(); |
| EXPECT_FALSE(state.has_media_app); |
| EXPECT_FALSE(state.has_camera_permission); |
| EXPECT_FALSE(state.has_microphone_permission); |
| EXPECT_FALSE(state.is_capturing_camera); |
| EXPECT_FALSE(state.is_capturing_microphone); |
| EXPECT_FALSE(state.is_capturing_screen); |
| |
| // Accessing camera should start a tracking of the kAppId1. |
| SetAppCapabilityAccess(kAppId1, /*is_capturing_camera=*/true, |
| /*is_capturing_microphone=*/false); |
| |
| state = GetMediaStateInVideoConferenceManagerAsh(); |
| EXPECT_TRUE(state.has_media_app); |
| EXPECT_TRUE(state.has_camera_permission); |
| EXPECT_TRUE(state.is_capturing_camera); |
| |
| // Accessing microphone should start a tracking of the kAppId2. |
| SetAppCapabilityAccess(kAppId2, /*is_capturing_camera=*/false, |
| /*is_capturing_microphone=*/true); |
| |
| state = GetMediaStateInVideoConferenceManagerAsh(); |
| EXPECT_TRUE(state.has_media_app); |
| EXPECT_TRUE(state.has_microphone_permission); |
| EXPECT_TRUE(state.is_capturing_microphone); |
| |
| // This should stop the accessing, but not the permission. |
| SetAppCapabilityAccess(kAppId1, /*is_capturing_camera=*/false, |
| /*is_capturing_microphone=*/false); |
| |
| state = GetMediaStateInVideoConferenceManagerAsh(); |
| EXPECT_TRUE(state.has_camera_permission); |
| EXPECT_FALSE(state.is_capturing_camera); |
| |
| // Closing instance1 should remove tracking of kAppId1. |
| instance1.Close(); |
| // Wait for the VideoConferenceAppServiceClient::MaybeRemoveApp to be called |
| // in the PostTask. |
| base::RunLoop().RunUntilIdle(); |
| |
| state = GetMediaStateInVideoConferenceManagerAsh(); |
| EXPECT_FALSE(state.has_camera_permission); |
| EXPECT_FALSE(state.is_capturing_camera); |
| |
| SetAppCapabilityAccess(kAppId2, /*is_capturing_camera=*/false, |
| /*is_capturing_microphone=*/false); |
| |
| state = GetMediaStateInVideoConferenceManagerAsh(); |
| EXPECT_TRUE(state.has_microphone_permission); |
| EXPECT_FALSE(state.is_capturing_microphone); |
| |
| // Closing instance2 should remove trackingg of kAppId2. |
| instance2.Close(); |
| // Wait for the VideoConferenceAppServiceClient::MaybeRemoveApp to be called |
| // in the PostTask. |
| base::RunLoop().RunUntilIdle(); |
| |
| state = GetMediaStateInVideoConferenceManagerAsh(); |
| EXPECT_FALSE(state.has_media_app); |
| EXPECT_FALSE(state.has_camera_permission); |
| EXPECT_FALSE(state.has_microphone_permission); |
| EXPECT_FALSE(state.is_capturing_camera); |
| EXPECT_FALSE(state.is_capturing_microphone); |
| EXPECT_FALSE(state.is_capturing_screen); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(VideoConferenceAppServiceClientTest, |
| OnlyCertainAppsAreTracked) { |
| for (const auto type : |
| {apps::AppType::kUnknown, apps::AppType::kBuiltIn, |
| apps::AppType::kCrostini, apps::AppType::kChromeApp, |
| apps::AppType::kWeb, apps::AppType::kMacOs, apps::AppType::kPluginVm, |
| apps::AppType::kStandaloneBrowser, apps::AppType::kRemote, |
| apps::AppType::kBorealis, apps::AppType::kSystemWeb, |
| apps::AppType::kStandaloneBrowserChromeApp, apps::AppType::kExtension, |
| apps::AppType::kStandaloneBrowserExtension, |
| apps::AppType::kBruschetta}) { |
| // Create a fake id. |
| const std::string app_id = base::NumberToString(static_cast<int>(type)); |
| // Install the app with given type. |
| InstallApp(app_id, type); |
| // Start the app. |
| FakeAppInstance instance(instance_registry_, app_id); |
| instance.Start(); |
| |
| // has-camera, has-mic should not start tracking of the app only because |
| // that we are not tracking the AppType listed above. |
| SetAppCapabilityAccess(app_id, /*is_capturing_camera=*/true, |
| /*is_capturing_microphone=*/true); |
| |
| EXPECT_TRUE(GetMediaApps().empty()); |
| } |
| } |
| |
| IN_PROC_BROWSER_TEST_F(VideoConferenceAppServiceClientTest, |
| HandleDeviceUsedWhileDisabled) { |
| // Notify disabling state of camera and microphone from |
| // video_conference_manager_ash. |
| crosapi::CrosapiManager::Get() |
| ->crosapi_ash() |
| ->video_conference_manager_ash() |
| ->SetSystemMediaDeviceStatus( |
| crosapi::mojom::VideoConferenceMediaDevice::kCamera, |
| /*disabled=*/true); |
| crosapi::CrosapiManager::Get() |
| ->crosapi_ash() |
| ->video_conference_manager_ash() |
| ->SetSystemMediaDeviceStatus( |
| crosapi::mojom::VideoConferenceMediaDevice::kMicrophone, |
| /*disabled=*/true); |
| |
| FakeVideoConferenceTrayController* fake_try_controller = |
| static_cast<FakeVideoConferenceTrayController*>( |
| VideoConferenceTrayController::Get()); |
| |
| InstallApp(kAppId1); |
| |
| // Accessing camera will trigger NotifyDeviceUsedWhileDisabled. |
| SetAppCapabilityAccess(kAppId1, /*is_capturing_camera=*/true, |
| /*is_capturing_microphone=*/false); |
| ASSERT_EQ(fake_try_controller->device_used_while_disabled_records().size(), |
| 1u); |
| EXPECT_THAT(fake_try_controller->device_used_while_disabled_records().back(), |
| testing::Pair(crosapi::mojom::VideoConferenceMediaDevice::kCamera, |
| base::UTF8ToUTF16(std::string(kAppName1)))); |
| |
| // Accessing microphone will trigger NotifyDeviceUsedWhileDisabled. |
| SetAppCapabilityAccess(kAppId1, /*is_capturing_camera=*/true, |
| /*is_capturing_microphone=*/true); |
| ASSERT_EQ(fake_try_controller->device_used_while_disabled_records().size(), |
| 2u); |
| EXPECT_THAT( |
| fake_try_controller->device_used_while_disabled_records().back(), |
| testing::Pair(crosapi::mojom::VideoConferenceMediaDevice::kMicrophone, |
| base::UTF8ToUTF16(std::string(kAppName1)))); |
| |
| // Stopping microphone access should not trigger |
| // NotifyDeviceUsedWhileDisabled. |
| SetAppCapabilityAccess(kAppId1, /*is_capturing_camera=*/true, |
| /*is_capturing_microphone=*/false); |
| ASSERT_EQ(fake_try_controller->device_used_while_disabled_records().size(), |
| 2u); |
| |
| // Stopping camera access should not trigger NotifyDeviceUsedWhileDisabled. |
| SetAppCapabilityAccess(kAppId1, /*is_capturing_camera=*/false, |
| /*is_capturing_microphone=*/false); |
| ASSERT_EQ(fake_try_controller->device_used_while_disabled_records().size(), |
| 2u); |
| |
| // Notify enabling state of camera and microphone from |
| // video_conference_manager_ash. |
| crosapi::CrosapiManager::Get() |
| ->crosapi_ash() |
| ->video_conference_manager_ash() |
| ->SetSystemMediaDeviceStatus( |
| crosapi::mojom::VideoConferenceMediaDevice::kCamera, |
| /*disabled=*/false); |
| crosapi::CrosapiManager::Get() |
| ->crosapi_ash() |
| ->video_conference_manager_ash() |
| ->SetSystemMediaDeviceStatus( |
| crosapi::mojom::VideoConferenceMediaDevice::kMicrophone, |
| /*disabled=*/false); |
| |
| // Accessing camera should not trigger NotifyDeviceUsedWhileDisabled because |
| // camera is not disabled. |
| SetAppCapabilityAccess(kAppId1, /*is_capturing_camera=*/true, |
| /*is_capturing_microphone=*/false); |
| ASSERT_EQ(fake_try_controller->device_used_while_disabled_records().size(), |
| 2u); |
| |
| // Accessing microphone should not trigger NotifyDeviceUsedWhileDisabled |
| // because microphone is not disabled. |
| SetAppCapabilityAccess(kAppId1, /*is_capturing_camera=*/true, |
| /*is_capturing_microphone=*/true); |
| ASSERT_EQ(fake_try_controller->device_used_while_disabled_records().size(), |
| 2u); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(VideoConferenceAppServiceClientTest, |
| SomeAppsAreNotTracked) { |
| // Install all apps that should be skipped. |
| for (const std::string& app_id : ::video_conference::kSkipAppIds) { |
| InstallApp(app_id); |
| // Accessing mic and camera should trigger tracking except the app_id is |
| // skipped. |
| SetAppCapabilityAccess(app_id, /*is_capturing_camera=*/true, |
| /*is_capturing_microphone=*/true); |
| } |
| |
| EXPECT_TRUE(GetMediaApps().empty()); |
| } |
| |
| } // namespace ash |