| // Copyright 2016 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/note_taking/note_taking_helper.h" |
| |
| #include <memory> |
| #include <sstream> |
| #include <string> |
| #include <utility> |
| |
| #include "ash/constants/ash_switches.h" |
| #include "ash/public/cpp/note_taking_client.h" |
| #include "ash/shell.h" |
| #include "base/command_line.h" |
| #include "base/feature_list.h" |
| #include "base/functional/bind.h" |
| #include "base/memory/raw_ptr.h" |
| #include "base/memory/scoped_refptr.h" |
| #include "base/run_loop.h" |
| #include "base/strings/string_util.h" |
| #include "base/strings/stringprintf.h" |
| #include "base/test/metrics/histogram_tester.h" |
| #include "base/test/scoped_feature_list.h" |
| #include "base/values.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/app_list/arc/arc_app_test.h" |
| #include "chrome/browser/ash/arc/fileapi/arc_file_system_bridge.h" |
| #include "chrome/browser/ash/login/users/fake_chrome_user_manager.h" |
| #include "chrome/browser/ash/note_taking/note_taking_controller_client.h" |
| #include "chrome/browser/ash/profiles/profile_helper.h" |
| #include "chrome/browser/extensions/extension_service.h" |
| #include "chrome/browser/extensions/test_extension_system.h" |
| #include "chrome/browser/media/router/media_router_feature.h" |
| #include "chrome/browser/prefs/browser_prefs.h" |
| #include "chrome/browser/profiles/profile.h" |
| #include "chrome/browser/ui/browser.h" |
| #include "chrome/browser/ui/tabs/tab_strip_model.h" |
| #include "chrome/browser/web_applications/test/fake_web_app_provider.h" |
| #include "chrome/browser/web_applications/test/web_app_install_test_utils.h" |
| #include "chrome/browser/web_applications/web_app_install_info.h" |
| #include "chrome/browser/web_applications/web_app_provider.h" |
| #include "chrome/browser/web_applications/web_app_registrar.h" |
| #include "chrome/common/pref_names.h" |
| #include "chrome/test/base/browser_with_test_window_test.h" |
| #include "chrome/test/base/testing_profile.h" |
| #include "chrome/test/base/testing_profile_manager.h" |
| #include "chrome/test/base/ui_test_utils.h" |
| #include "chromeos/ash/components/dbus/cros_disks/cros_disks_client.h" |
| #include "chromeos/ash/components/dbus/session_manager/fake_session_manager_client.h" |
| #include "chromeos/ash/components/dbus/session_manager/session_manager_client.h" |
| #include "chromeos/ash/components/disks/disk.h" |
| #include "chromeos/ash/components/disks/disk_mount_manager.h" |
| #include "chromeos/ash/experiences/arc/arc_prefs.h" |
| #include "chromeos/ash/experiences/arc/mojom/file_system.mojom.h" |
| #include "chromeos/ash/experiences/arc/mojom/intent_common.mojom.h" |
| #include "chromeos/ash/experiences/arc/mojom/intent_helper.mojom.h" |
| #include "chromeos/ash/experiences/arc/session/arc_bridge_service.h" |
| #include "chromeos/ash/experiences/arc/session/arc_service_manager.h" |
| #include "chromeos/ash/experiences/arc/session/connection_holder.h" |
| #include "chromeos/ash/experiences/arc/test/connection_holder_util.h" |
| #include "chromeos/ash/experiences/arc/test/fake_file_system_instance.h" |
| #include "chromeos/ash/experiences/arc/test/fake_intent_helper_host.h" |
| #include "chromeos/ash/experiences/arc/test/fake_intent_helper_instance.h" |
| #include "components/crx_file/id_util.h" |
| #include "components/prefs/pref_service.h" |
| #include "components/sync_preferences/pref_service_syncable.h" |
| #include "components/sync_preferences/testing_pref_service_syncable.h" |
| #include "components/user_manager/test_helper.h" |
| #include "content/public/browser/web_contents.h" |
| #include "content/public/common/content_features.h" |
| #include "extensions/browser/extension_registrar.h" |
| #include "extensions/browser/extension_system.h" |
| #include "extensions/browser/uninstall_reason.h" |
| #include "extensions/common/api/app_runtime.h" |
| #include "extensions/common/extension.h" |
| #include "extensions/common/extension_builder.h" |
| #include "extensions/common/extension_id.h" |
| #include "google_apis/gaia/gaia_id.h" |
| #include "mojo/public/cpp/bindings/struct_ptr.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| #include "third_party/skia/include/core/SkTypes.h" |
| #include "ui/display/test/display_manager_test_api.h" |
| #include "url/gurl.h" |
| |
| namespace ash { |
| |
| namespace app_runtime = extensions::api::app_runtime; |
| |
| using ::arc::mojom::IntentHandlerInfo; |
| using ::arc::mojom::IntentHandlerInfoPtr; |
| using ::base::HistogramTester; |
| using HandledIntent = ::arc::FakeIntentHelperInstance::HandledIntent; |
| using LaunchResult = NoteTakingHelper::LaunchResult; |
| |
| namespace { |
| |
| auto& kDevKeepExtensionId = NoteTakingHelper::kDevKeepExtensionId; |
| auto& kProdKeepExtensionId = NoteTakingHelper::kProdKeepExtensionId; |
| |
| // Name of default profile. |
| constexpr char kTestProfileName[] = "test-profile"; |
| constexpr char kSecondProfileName[] = "second-profile"; |
| constexpr GaiaId::Literal kFakeGaia2("fakegaia2"); |
| |
| // Names for keep apps used in tests. |
| constexpr char kProdKeepAppName[] = "Google Keep [prod]"; |
| constexpr char kDevKeepAppName[] = "Google Keep [dev]"; |
| |
| std::string GetAppString(const std::string& name, |
| const std::string& id, |
| bool preferred) { |
| return base::StringPrintf("{%s, %s, %d}", name.c_str(), id.c_str(), |
| preferred); |
| } |
| std::string GetAppString(const NoteTakingAppInfo& info) { |
| return GetAppString(info.name, info.app_id, info.preferred); |
| } |
| |
| // Creates an ARC IntentHandlerInfo object. |
| IntentHandlerInfoPtr CreateIntentHandlerInfo(const std::string& name, |
| const std::string& package) { |
| IntentHandlerInfoPtr handler = IntentHandlerInfo::New(); |
| handler->name = name; |
| handler->package_name = package; |
| return handler; |
| } |
| |
| // Implementation of NoteTakingHelper::Observer for testing. |
| class TestObserver : public NoteTakingHelper::Observer { |
| public: |
| TestObserver() { NoteTakingHelper::Get()->AddObserver(this); } |
| |
| TestObserver(const TestObserver&) = delete; |
| TestObserver& operator=(const TestObserver&) = delete; |
| |
| ~TestObserver() override { NoteTakingHelper::Get()->RemoveObserver(this); } |
| |
| int num_updates() const { return num_updates_; } |
| void reset_num_updates() { num_updates_ = 0; } |
| |
| const std::vector<raw_ptr<Profile>> preferred_app_updates() const { |
| return preferred_app_updates_; |
| } |
| void clear_preferred_app_updates() { preferred_app_updates_.clear(); } |
| |
| private: |
| // NoteTakingHelper::Observer: |
| void OnAvailableNoteTakingAppsUpdated() override { num_updates_++; } |
| |
| void OnPreferredNoteTakingAppUpdated(Profile* profile) override { |
| preferred_app_updates_.push_back(profile); |
| } |
| |
| // Number of times that OnAvailableNoteTakingAppsUpdated() has been called. |
| int num_updates_ = 0; |
| |
| // Profiles for which OnPreferredNoteTakingAppUpdated was called. |
| std::vector<raw_ptr<Profile>> preferred_app_updates_; |
| }; |
| |
| } // namespace |
| |
| class NoteTakingHelperTest : public BrowserWithTestWindowTest { |
| public: |
| NoteTakingHelperTest() { |
| // `media_router::kMediaRouter` is disabled because it has unmet |
| // dependencies and is unrelated to this unit test. |
| feature_list_.InitAndDisableFeature(media_router::kMediaRouter); |
| } |
| |
| NoteTakingHelperTest(const NoteTakingHelperTest&) = delete; |
| NoteTakingHelperTest& operator=(const NoteTakingHelperTest&) = delete; |
| |
| ~NoteTakingHelperTest() override = default; |
| |
| void SetUp() override { |
| ash::ProfileHelper::SetProfileToUserForTestingEnabled(true); |
| SessionManagerClient::InitializeFakeInMemory(); |
| FakeSessionManagerClient::Get()->set_arc_available(true); |
| |
| BrowserWithTestWindowTest::SetUp(); |
| InitExtensionService(profile()); |
| InitWebAppProvider(profile()); |
| } |
| |
| void TearDown() override { |
| if (initialized_) { |
| arc::ArcServiceManager::Get() |
| ->arc_bridge_service() |
| ->intent_helper() |
| ->CloseInstance(&intent_helper_); |
| arc::ArcServiceManager::Get() |
| ->arc_bridge_service() |
| ->file_system() |
| ->CloseInstance(file_system_.get()); |
| NoteTakingHelper::Shutdown(); |
| intent_helper_host_.reset(); |
| file_system_bridge_.reset(); |
| arc_test_.TearDown(); |
| } |
| BrowserWithTestWindowTest::TearDown(); |
| SessionManagerClient::Shutdown(); |
| ash::ProfileHelper::SetProfileToUserForTestingEnabled(false); |
| } |
| |
| protected: |
| // Information about a Chrome app passed to LaunchChromeApp(). |
| struct ChromeAppLaunchInfo { |
| extensions::ExtensionId id; |
| }; |
| |
| // Options that can be passed to Init(). |
| enum InitFlags : uint32_t { |
| ENABLE_PLAY_STORE = 1 << 0, |
| ENABLE_PALETTE = 1 << 1, |
| }; |
| |
| static NoteTakingHelper* helper() { return NoteTakingHelper::Get(); } |
| |
| NoteTakingControllerClient* note_taking_client() { |
| return helper()->GetNoteTakingControllerClientForTesting(); |
| } |
| |
| void SetNoteTakingClientProfile(Profile* profile) { |
| if (note_taking_client()) |
| note_taking_client()->SetProfileForTesting(profile); |
| } |
| |
| // Initializes ARC and NoteTakingHelper. |flags| contains OR-ed together |
| // InitFlags values. |
| void Init(uint32_t flags) { |
| ASSERT_FALSE(initialized_); |
| initialized_ = true; |
| |
| profile()->GetPrefs()->SetBoolean(arc::prefs::kArcEnabled, |
| flags & ENABLE_PLAY_STORE); |
| arc_test_.SetUp(profile()); |
| // Set up FakeIntentHelperHost to emulate full-duplex IntentHelper |
| // connection. |
| intent_helper_host_ = std::make_unique<arc::FakeIntentHelperHost>( |
| arc::ArcServiceManager::Get()->arc_bridge_service()->intent_helper()); |
| arc::ArcServiceManager::Get() |
| ->arc_bridge_service() |
| ->intent_helper() |
| ->SetInstance(&intent_helper_); |
| WaitForInstanceReady( |
| arc::ArcServiceManager::Get()->arc_bridge_service()->intent_helper()); |
| |
| file_system_bridge_ = std::make_unique<arc::ArcFileSystemBridge>( |
| profile(), arc::ArcServiceManager::Get()->arc_bridge_service()); |
| file_system_ = std::make_unique<arc::FakeFileSystemInstance>(); |
| |
| arc::ArcServiceManager::Get() |
| ->arc_bridge_service() |
| ->file_system() |
| ->SetInstance(file_system_.get()); |
| WaitForInstanceReady( |
| arc::ArcServiceManager::Get()->arc_bridge_service()->file_system()); |
| ASSERT_TRUE(file_system_->InitCalled()); |
| |
| if (flags & ENABLE_PALETTE) { |
| base::CommandLine::ForCurrentProcess()->AppendSwitch( |
| switches::kAshForceEnableStylusTools); |
| } |
| |
| // TODO(derat): Sigh, something in ArcAppTest appears to be re-enabling ARC. |
| profile()->GetPrefs()->SetBoolean(arc::prefs::kArcEnabled, |
| flags & ENABLE_PLAY_STORE); |
| NoteTakingHelper::Initialize(); |
| NoteTakingHelper::Get()->set_launch_chrome_app_callback_for_test( |
| base::BindRepeating(&NoteTakingHelperTest::LaunchChromeApp, |
| base::Unretained(this))); |
| } |
| |
| // Creates an extension. |
| scoped_refptr<const extensions::Extension> CreateExtension( |
| const extensions::ExtensionId& id, |
| const std::string& name) { |
| return CreateExtension(id, name, std::nullopt, std::nullopt); |
| } |
| scoped_refptr<const extensions::Extension> CreateExtension( |
| const extensions::ExtensionId& id, |
| const std::string& name, |
| std::optional<base::Value::List> permissions, |
| std::optional<base::Value::List> action_handlers) { |
| base::Value::Dict manifest = |
| base::Value::Dict() |
| .Set("name", name) |
| .Set("version", "1.0") |
| .Set("manifest_version", 2) |
| .Set("app", base::Value::Dict().Set( |
| "background", |
| base::Value::Dict().Set( |
| "scripts", |
| base::Value::List().Append("background.js")))); |
| |
| if (action_handlers) |
| manifest.Set("action_handlers", std::move(*action_handlers)); |
| |
| if (permissions) |
| manifest.Set("permissions", std::move(*permissions)); |
| |
| return extensions::ExtensionBuilder() |
| .SetManifest(std::move(manifest)) |
| .SetID(id) |
| .Build(); |
| } |
| |
| void InitWebAppProvider(Profile* profile) { |
| web_app::test::AwaitStartWebAppProviderAndSubsystems(profile); |
| } |
| |
| // Initializes extensions-related objects for |profile|. Tests only need to |
| // call this if they create additional profiles of their own. |
| void InitExtensionService(Profile* profile) { |
| extensions::TestExtensionSystem* extension_system = |
| static_cast<extensions::TestExtensionSystem*>( |
| extensions::ExtensionSystem::Get(profile)); |
| extension_system->CreateExtensionService( |
| base::CommandLine::ForCurrentProcess(), |
| base::FilePath() /* install_directory */, |
| false /* autoupdate_enabled */); |
| } |
| |
| // Installs or uninstalls |extension| in |profile|. |
| void InstallExtension(const extensions::Extension* extension, |
| Profile* profile) { |
| extensions::ExtensionRegistrar::Get(profile)->AddExtension(extension); |
| } |
| void UninstallExtension(const extensions::Extension* extension, |
| Profile* profile) { |
| std::u16string error; |
| extensions::ExtensionRegistrar::Get(profile)->UninstallExtension( |
| extension->id(), |
| extensions::UninstallReason::UNINSTALL_REASON_FOR_TESTING, &error); |
| } |
| |
| // BrowserWithTestWindowTest: |
| std::optional<std::string> GetDefaultProfileName() override { |
| return kTestProfileName; |
| } |
| |
| // TODO(crbug.com/40286020): merge into BrowserWithTestWindowTest. |
| void LogIn(std::string_view email, const GaiaId& gaia_id) override { |
| AccountId account_id = AccountId::FromUserEmailGaiaId(email, gaia_id); |
| user_manager()->AddGaiaUser(account_id, user_manager::UserType::kRegular); |
| user_manager()->UserLoggedIn( |
| account_id, user_manager::TestHelper::GetFakeUsernameHash(account_id)); |
| } |
| |
| TestingProfile* CreateProfile(const std::string& profile_name) override { |
| auto prefs = |
| std::make_unique<sync_preferences::TestingPrefServiceSyncable>(); |
| RegisterUserProfilePrefs(prefs->registry()); |
| profile_prefs_ = prefs.get(); |
| auto* profile = profile_manager()->CreateTestingProfile( |
| profile_name, std::move(prefs), u"Test profile", 1 /*avatar_id*/, |
| TestingProfile::TestingFactories()); |
| return profile; |
| } |
| |
| TestingProfile* CreateAndInitSecondaryProfile() { |
| auto prefs = |
| std::make_unique<sync_preferences::TestingPrefServiceSyncable>(); |
| RegisterUserProfilePrefs(prefs->registry()); |
| const AccountId account_id( |
| AccountId::FromUserEmailGaiaId(kSecondProfileName, kFakeGaia2)); |
| user_manager()->AddGaiaUser(account_id, user_manager::UserType::kRegular); |
| TestingProfile* profile = profile_manager()->CreateTestingProfile( |
| kSecondProfileName, std::move(prefs), u"second-profile-username", |
| /*avatar_id=*/1, TestingProfile::TestingFactories()); |
| |
| InitExtensionService(profile); |
| InitWebAppProvider(profile); |
| DCHECK(!ash::ProfileHelper::IsPrimaryProfile(profile)); |
| |
| return profile; |
| } |
| |
| std::string NoteAppInfoListToString( |
| const std::vector<NoteTakingAppInfo>& apps) { |
| std::vector<std::string> app_strings; |
| for (const auto& app : apps) |
| app_strings.push_back(GetAppString(app)); |
| return base::JoinString(app_strings, ","); |
| } |
| |
| testing::AssertionResult AvailableAppsMatch( |
| Profile* profile, |
| const std::vector<NoteTakingAppInfo>& expected_apps) { |
| std::vector<NoteTakingAppInfo> actual_apps = |
| helper()->GetAvailableApps(profile); |
| if (actual_apps.size() != expected_apps.size()) { |
| return ::testing::AssertionFailure() |
| << "Size mismatch. " |
| << "Expected: [" << NoteAppInfoListToString(expected_apps) << "] " |
| << "Actual: [" << NoteAppInfoListToString(actual_apps) << "]"; |
| } |
| |
| std::unique_ptr<::testing::AssertionResult> failure; |
| for (size_t i = 0; i < expected_apps.size(); ++i) { |
| std::string expected = GetAppString(expected_apps[i]); |
| std::string actual = GetAppString(actual_apps[i]); |
| if (expected != actual) { |
| if (!failure) { |
| failure = std::make_unique<::testing::AssertionResult>( |
| ::testing::AssertionFailure()); |
| } |
| *failure << "Error at index " << i << ": " |
| << "Expected: " << expected << " " |
| << "Actual: " << actual; |
| } |
| } |
| |
| if (failure) |
| return *failure; |
| return ::testing::AssertionSuccess(); |
| } |
| |
| // Info about launched Chrome apps, in the order they were launched. |
| std::vector<ChromeAppLaunchInfo> launched_chrome_apps_; |
| |
| arc::FakeIntentHelperInstance intent_helper_; |
| |
| std::unique_ptr<arc::ArcFileSystemBridge> file_system_bridge_; |
| |
| std::unique_ptr<arc::FakeFileSystemInstance> file_system_; |
| |
| // Pointer to the primary profile (returned by |profile()|) prefs - owned by |
| // the profile. |
| raw_ptr<sync_preferences::TestingPrefServiceSyncable, DanglingUntriaged> |
| profile_prefs_ = nullptr; |
| |
| private: |
| // Callback registered with the helper to record Chrome app launch requests. |
| void LaunchChromeApp(content::BrowserContext* passed_context, |
| const extensions::Extension* extension) { |
| EXPECT_EQ(profile(), passed_context); |
| launched_chrome_apps_.push_back(ChromeAppLaunchInfo{extension->id()}); |
| } |
| |
| // Has Init() been called? |
| bool initialized_ = false; |
| |
| ArcAppTest arc_test_{ArcAppTest::UserManagerMode::kDoNothing}; |
| std::unique_ptr<arc::FakeIntentHelperHost> intent_helper_host_; |
| base::test::ScopedFeatureList feature_list_; |
| }; |
| |
| TEST_F(NoteTakingHelperTest, PaletteNotEnabled) { |
| // Without the palette enabled, IsAppAvailable() should return false. |
| Init(0); |
| scoped_refptr<const extensions::Extension> extension = |
| CreateExtension(kProdKeepExtensionId, "Keep"); |
| InstallExtension(extension.get(), profile()); |
| EXPECT_FALSE(helper()->IsAppAvailable(profile())); |
| } |
| |
| TEST_F(NoteTakingHelperTest, ListChromeApps) { |
| Init(ENABLE_PALETTE); |
| |
| // Start out without any note-taking apps installed. |
| EXPECT_FALSE(helper()->IsAppAvailable(profile())); |
| EXPECT_TRUE(helper()->GetAvailableApps(profile()).empty()); |
| |
| // If only the prod version of the app is installed, it should be returned. |
| scoped_refptr<const extensions::Extension> prod_extension = |
| CreateExtension(kProdKeepExtensionId, kProdKeepAppName); |
| InstallExtension(prod_extension.get(), profile()); |
| EXPECT_TRUE(helper()->IsAppAvailable(profile())); |
| EXPECT_TRUE(AvailableAppsMatch( |
| profile(), |
| {{kProdKeepAppName, kProdKeepExtensionId, false /*preferred*/}})); |
| |
| // If the dev version is also installed, it should be listed before the prod |
| // version. |
| scoped_refptr<const extensions::Extension> dev_extension = |
| CreateExtension(kDevKeepExtensionId, kDevKeepAppName); |
| InstallExtension(dev_extension.get(), profile()); |
| EXPECT_TRUE(AvailableAppsMatch( |
| profile(), |
| {{kDevKeepAppName, kDevKeepExtensionId, false /*preferred*/}, |
| {kProdKeepAppName, kProdKeepExtensionId, false /*preferred*/}})); |
| EXPECT_TRUE(helper()->GetPreferredAppId(profile()).empty()); |
| |
| // Now install a random web app to check that it's ignored. |
| web_app::test::InstallDummyWebApp(profile(), "Web App", |
| GURL("http://some.url")); |
| // Now install a random extension to check that it's ignored. |
| const extensions::ExtensionId kOtherId = crx_file::id_util::GenerateId("a"); |
| const std::string kOtherName = "Some Other App"; |
| scoped_refptr<const extensions::Extension> other_extension = |
| CreateExtension(kOtherId, kOtherName); |
| InstallExtension(other_extension.get(), profile()); |
| |
| EXPECT_TRUE(AvailableAppsMatch( |
| profile(), |
| {{kDevKeepAppName, kDevKeepExtensionId, false /*preferred*/}, |
| {kProdKeepAppName, kProdKeepExtensionId, false /*preferred*/}})); |
| EXPECT_TRUE(helper()->GetPreferredAppId(profile()).empty()); |
| |
| // Mark the prod version as preferred. |
| helper()->SetPreferredApp(profile(), kProdKeepExtensionId); |
| EXPECT_TRUE(AvailableAppsMatch( |
| profile(), |
| {{kDevKeepAppName, kDevKeepExtensionId, false /*preferred*/}, |
| {kProdKeepAppName, kProdKeepExtensionId, true /*preferred*/}})); |
| EXPECT_EQ(helper()->GetPreferredAppId(profile()), kProdKeepExtensionId); |
| } |
| |
| // Web apps with a note_taking_new_note_url show as available note-taking apps. |
| TEST_F(NoteTakingHelperTest, NoteTakingWebAppsListed) { |
| Init(ENABLE_PALETTE); |
| |
| { |
| auto app_info = web_app::WebAppInstallInfo::CreateWithStartUrlForTesting( |
| GURL("http://some1.url")); |
| app_info->scope = GURL("http://some1.url"); |
| app_info->title = u"Web App 1"; |
| web_app::test::InstallWebApp(profile(), std::move(app_info)); |
| } |
| std::string app2_id; |
| { |
| auto app_info = web_app::WebAppInstallInfo::CreateWithStartUrlForTesting( |
| GURL("http://some2.url")); |
| app_info->scope = GURL("http://some2.url"); |
| app_info->title = u"Web App 2"; |
| // Set a note_taking_new_note_url on one app. |
| app_info->note_taking_new_note_url = GURL("http://some2.url/new-note"); |
| app2_id = web_app::test::InstallWebApp(profile(), std::move(app_info)); |
| } |
| // Check apps were installed. |
| auto* provider = web_app::WebAppProvider::GetForTest(profile()); |
| EXPECT_EQ(provider->registrar_unsafe().CountUserInstalledApps(), 2); |
| |
| // Apps with note_taking_new_note_url are listed. |
| EXPECT_TRUE(AvailableAppsMatch( |
| profile(), {{"Web App 2", app2_id, false /*preferred*/}})); |
| } |
| |
| // Web apps with a lock_screen_start_url should show as supported on the lock |
| // screen only when `kWebLockScreenApi` is enabled. |
| // TODO(crbug.com/40227659): Move this to a lock screen apps unittest file. |
| TEST_F(NoteTakingHelperTest, LockScreenWebAppsListed) { |
| Init(ENABLE_PALETTE); |
| DCHECK(!base::FeatureList::IsEnabled(::features::kWebLockScreenApi)); |
| |
| std::string app1_id; |
| { |
| auto app_info = web_app::WebAppInstallInfo::CreateWithStartUrlForTesting( |
| GURL("http://some1.url")); |
| app_info->scope = GURL("http://some1.url"); |
| app_info->title = u"Web App 1"; |
| // Currently only note-taking apps can be used on the lock screen. |
| app_info->note_taking_new_note_url = GURL("http://some2.url/new-note"); |
| app1_id = web_app::test::InstallWebApp(profile(), std::move(app_info)); |
| } |
| std::string app2_id; |
| { |
| auto app_info = web_app::WebAppInstallInfo::CreateWithStartUrlForTesting( |
| GURL("http://some2.url")); |
| app_info->scope = GURL("http://some2.url"); |
| app_info->title = u"Web App 2"; |
| app_info->note_taking_new_note_url = GURL("http://some2.url/new-note"); |
| // Set a lock_screen_start_url on one app. |
| app_info->lock_screen_start_url = |
| GURL("http://some2.url/lock-screen-start"); |
| app2_id = web_app::test::InstallWebApp(profile(), std::move(app_info)); |
| } |
| // Check apps were installed. |
| auto* provider = web_app::WebAppProvider::GetForTest(profile()); |
| EXPECT_EQ(provider->registrar_unsafe().CountUserInstalledApps(), 2); |
| |
| // With the flag disabled, web apps are not supported. |
| EXPECT_TRUE(AvailableAppsMatch( |
| profile(), {{"Web App 1", app1_id, /*preferred=*/false}, |
| {"Web App 2", app2_id, /*preferred=*/false}})); |
| } |
| |
| class NoteTakingHelperTest_WebLockScreenApiEnabled |
| : public NoteTakingHelperTest { |
| base::test::ScopedFeatureList features_{::features::kWebLockScreenApi}; |
| }; |
| |
| // Web apps with a lock_screen_start_url should show as supported on the lock |
| // screen only when `kWebLockScreenApi` is enabled. |
| // TODO(crbug.com/40227659): Move this to a lock screen apps unittest file. |
| TEST_F(NoteTakingHelperTest_WebLockScreenApiEnabled, LockScreenWebAppsListed) { |
| Init(ENABLE_PALETTE); |
| DCHECK(base::FeatureList::IsEnabled(::features::kWebLockScreenApi)); |
| |
| std::string app1_id; |
| { |
| auto app_info = web_app::WebAppInstallInfo::CreateWithStartUrlForTesting( |
| GURL("http://some1.url")); |
| app_info->scope = GURL("http://some1.url"); |
| app_info->title = u"Web App 1"; |
| // Currently only note-taking apps can be used on the lock screen. |
| app_info->note_taking_new_note_url = GURL("http://some2.url/new-note"); |
| app1_id = web_app::test::InstallWebApp(profile(), std::move(app_info)); |
| } |
| std::string app2_id; |
| { |
| auto app_info = web_app::WebAppInstallInfo::CreateWithStartUrlForTesting( |
| GURL("http://some2.url")); |
| app_info->scope = GURL("http://some2.url"); |
| app_info->title = u"Web App 2"; |
| app_info->note_taking_new_note_url = GURL("http://some2.url/new-note"); |
| // Set a lock_screen_start_url on one app. |
| app_info->lock_screen_start_url = |
| GURL("http://some2.url/lock-screen-start"); |
| app2_id = web_app::test::InstallWebApp(profile(), std::move(app_info)); |
| } |
| // Check apps were installed. |
| auto* provider = web_app::WebAppProvider::GetForTest(profile()); |
| EXPECT_EQ(provider->registrar_unsafe().CountUserInstalledApps(), 2); |
| |
| // The web app with a lock screen start URL is supported. |
| EXPECT_TRUE(AvailableAppsMatch( |
| profile(), {{"Web App 1", app1_id, /*preferred=*/false}, |
| {"Web App 2", app2_id, /*preferred=*/false}})); |
| } |
| |
| TEST_F(NoteTakingHelperTest, LaunchChromeApp) { |
| Init(ENABLE_PALETTE); |
| scoped_refptr<const extensions::Extension> extension = |
| CreateExtension(kProdKeepExtensionId, "Keep"); |
| InstallExtension(extension.get(), profile()); |
| |
| // Check the Chrome app is launched with the correct parameters. |
| HistogramTester histogram_tester; |
| helper()->LaunchAppForNewNote(profile()); |
| ASSERT_EQ(1u, launched_chrome_apps_.size()); |
| EXPECT_EQ(kProdKeepExtensionId, launched_chrome_apps_[0].id); |
| |
| histogram_tester.ExpectUniqueSample( |
| NoteTakingHelper::kPreferredLaunchResultHistogramName, |
| static_cast<int>(LaunchResult::NO_APP_SPECIFIED), 1); |
| histogram_tester.ExpectUniqueSample( |
| NoteTakingHelper::kDefaultLaunchResultHistogramName, |
| static_cast<int>(LaunchResult::CHROME_SUCCESS), 1); |
| } |
| |
| TEST_F(NoteTakingHelperTest, FallBackIfPreferredAppUnavailable) { |
| Init(ENABLE_PALETTE); |
| scoped_refptr<const extensions::Extension> prod_extension = |
| CreateExtension(kProdKeepExtensionId, "prod"); |
| InstallExtension(prod_extension.get(), profile()); |
| scoped_refptr<const extensions::Extension> dev_extension = |
| CreateExtension(kDevKeepExtensionId, "dev"); |
| InstallExtension(dev_extension.get(), profile()); |
| { |
| // Install a default-allowed web app corresponding to ID of |
| // |NoteTakingHelper::kNoteTakingWebAppIdTest|. |
| auto app_info = web_app::WebAppInstallInfo::CreateWithStartUrlForTesting( |
| GURL("https://yielding-large-chef.glitch.me/")); |
| app_info->title = u"Default Allowed Web App"; |
| std::string app_id = |
| web_app::test::InstallWebApp(profile(), std::move(app_info)); |
| EXPECT_EQ(app_id, NoteTakingHelper::kNoteTakingWebAppIdTest); |
| } |
| |
| // Set the prod app as preferred and check that it's launched. |
| std::unique_ptr<HistogramTester> histogram_tester(new HistogramTester()); |
| helper()->SetPreferredApp(profile(), kProdKeepExtensionId); |
| helper()->LaunchAppForNewNote(profile()); |
| ASSERT_EQ(1u, launched_chrome_apps_.size()); |
| ASSERT_EQ(kProdKeepExtensionId, launched_chrome_apps_[0].id); |
| |
| histogram_tester->ExpectUniqueSample( |
| NoteTakingHelper::kPreferredLaunchResultHistogramName, |
| static_cast<int>(LaunchResult::CHROME_SUCCESS), 1); |
| histogram_tester->ExpectTotalCount( |
| NoteTakingHelper::kDefaultLaunchResultHistogramName, 0); |
| |
| // Now uninstall the prod app and check that we fall back to the dev app. |
| UninstallExtension(prod_extension.get(), profile()); |
| launched_chrome_apps_.clear(); |
| histogram_tester = std::make_unique<HistogramTester>(); |
| helper()->LaunchAppForNewNote(profile()); |
| ASSERT_EQ(1u, launched_chrome_apps_.size()); |
| EXPECT_EQ(kDevKeepExtensionId, launched_chrome_apps_[0].id); |
| |
| histogram_tester->ExpectUniqueSample( |
| NoteTakingHelper::kPreferredLaunchResultHistogramName, |
| static_cast<int>(LaunchResult::CHROME_APP_MISSING), 1); |
| histogram_tester->ExpectUniqueSample( |
| NoteTakingHelper::kDefaultLaunchResultHistogramName, |
| static_cast<int>(LaunchResult::CHROME_SUCCESS), 1); |
| |
| // Now uninstall the dev app and check that we fall back to the test web app. |
| UninstallExtension(dev_extension.get(), profile()); |
| launched_chrome_apps_.clear(); |
| histogram_tester = std::make_unique<HistogramTester>(); |
| helper()->LaunchAppForNewNote(profile()); |
| // Not a chrome app. |
| EXPECT_EQ(0u, launched_chrome_apps_.size()); |
| |
| histogram_tester->ExpectUniqueSample( |
| NoteTakingHelper::kPreferredLaunchResultHistogramName, |
| static_cast<int>(LaunchResult::CHROME_APP_MISSING), 1); |
| histogram_tester->ExpectUniqueSample( |
| NoteTakingHelper::kDefaultLaunchResultHistogramName, |
| static_cast<int>(LaunchResult::WEB_APP_SUCCESS), 1); |
| } |
| |
| TEST_F(NoteTakingHelperTest, PlayStoreInitiallyDisabled) { |
| Init(ENABLE_PALETTE); |
| EXPECT_FALSE(helper()->play_store_enabled()); |
| EXPECT_FALSE(helper()->android_apps_received()); |
| |
| // When Play Store is enabled, the helper's members should be updated |
| // accordingly. |
| profile()->GetPrefs()->SetBoolean(arc::prefs::kArcEnabled, true); |
| EXPECT_TRUE(helper()->play_store_enabled()); |
| EXPECT_FALSE(helper()->android_apps_received()); |
| |
| // After the callback to receive intent handlers has run, the "apps received" |
| // member should be updated (even if there aren't any apps). |
| helper()->OnIntentFiltersUpdated(std::nullopt); |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_TRUE(helper()->play_store_enabled()); |
| EXPECT_TRUE(helper()->android_apps_received()); |
| } |
| |
| TEST_F(NoteTakingHelperTest, AddProfileWithPlayStoreEnabled) { |
| Init(ENABLE_PALETTE); |
| EXPECT_FALSE(helper()->play_store_enabled()); |
| EXPECT_FALSE(helper()->android_apps_received()); |
| |
| TestObserver observer; |
| ASSERT_EQ(0, observer.num_updates()); |
| |
| // Add a second profile with the ARC-enabled pref already set. The Play Store |
| // should be immediately regarded as being enabled and the observer should be |
| // notified, since OnArcPlayStoreEnabledChanged() apparently isn't called in |
| // this case: http://crbug.com/700554 |
| auto prefs = std::make_unique<sync_preferences::TestingPrefServiceSyncable>(); |
| RegisterUserProfilePrefs(prefs->registry()); |
| prefs->SetBoolean(arc::prefs::kArcEnabled, true); |
| profile_manager()->CreateTestingProfile(kSecondProfileName, std::move(prefs), |
| u"Second User", 1 /* avatar_id */, |
| TestingProfile::TestingFactories()); |
| EXPECT_TRUE(helper()->play_store_enabled()); |
| EXPECT_FALSE(helper()->android_apps_received()); |
| EXPECT_EQ(1, observer.num_updates()); |
| |
| // TODO(derat|hidehiko): Check that NoteTakingHelper adds itself as an |
| // observer of the ArcIntentHelperBridge corresponding to the new profile: |
| // https://crbug.com/748763 |
| |
| // Notification of updated intent filters should result in the apps being |
| // refreshed. |
| helper()->OnIntentFiltersUpdated(std::nullopt); |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_TRUE(helper()->play_store_enabled()); |
| EXPECT_TRUE(helper()->android_apps_received()); |
| EXPECT_EQ(2, observer.num_updates()); |
| } |
| |
| TEST_F(NoteTakingHelperTest, ListAndroidApps) { |
| // Add two Android apps. |
| std::vector<IntentHandlerInfoPtr> handlers; |
| const std::string kName1 = "App 1"; |
| const std::string kPackage1 = "org.chromium.package1"; |
| handlers.emplace_back(CreateIntentHandlerInfo(kName1, kPackage1)); |
| const std::string kName2 = "App 2"; |
| const std::string kPackage2 = "org.chromium.package2"; |
| handlers.emplace_back(CreateIntentHandlerInfo(kName2, kPackage2)); |
| intent_helper_.SetIntentHandlers(NoteTakingHelper::kIntentAction, |
| std::move(handlers)); |
| |
| // NoteTakingHelper should make an async request for Android apps when |
| // constructed. |
| Init(ENABLE_PALETTE | ENABLE_PLAY_STORE); |
| EXPECT_TRUE(helper()->play_store_enabled()); |
| EXPECT_FALSE(helper()->android_apps_received()); |
| EXPECT_TRUE(helper()->GetAvailableApps(profile()).empty()); |
| |
| // The apps should be listed after the callback has had a chance to run. |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_TRUE(helper()->play_store_enabled()); |
| EXPECT_TRUE(helper()->android_apps_received()); |
| EXPECT_TRUE(helper()->IsAppAvailable(profile())); |
| EXPECT_TRUE(AvailableAppsMatch(profile(), |
| {{kName1, kPackage1, false /*preferred*/}, |
| {kName2, kPackage2, false /*preferred*/}})); |
| |
| helper()->SetPreferredApp(profile(), kPackage1); |
| |
| EXPECT_TRUE(AvailableAppsMatch(profile(), |
| {{kName1, kPackage1, true /*preferred*/}, |
| {kName2, kPackage2, false /*preferred*/}})); |
| // Preferred app is not actually installed, so no app ID should be returned. |
| EXPECT_TRUE(helper()->GetPreferredAppId(profile()).empty()); |
| |
| // Disable Play Store and check that the apps are no longer returned. |
| profile()->GetPrefs()->SetBoolean(arc::prefs::kArcEnabled, false); |
| EXPECT_FALSE(helper()->play_store_enabled()); |
| EXPECT_FALSE(helper()->android_apps_received()); |
| EXPECT_FALSE(helper()->IsAppAvailable(profile())); |
| EXPECT_TRUE(helper()->GetAvailableApps(profile()).empty()); |
| } |
| |
| TEST_F(NoteTakingHelperTest, LaunchAndroidAppNoDisplay) { |
| // Opening Android apps via OpenUrlsWithPermissionAndWindowInfo requires a |
| // valid internal display, not being able to find one will halt launch. |
| const std::string kPackage1 = "org.chromium.package1"; |
| std::vector<IntentHandlerInfoPtr> handlers; |
| handlers.emplace_back(CreateIntentHandlerInfo("App 1", kPackage1)); |
| intent_helper_.SetIntentHandlers(NoteTakingHelper::kIntentAction, |
| std::move(handlers)); |
| |
| Init(ENABLE_PALETTE | ENABLE_PLAY_STORE); |
| base::RunLoop().RunUntilIdle(); |
| ASSERT_TRUE(helper()->IsAppAvailable(profile())); |
| |
| // The installed app fails to launch, registering on histogram. |
| std::unique_ptr<HistogramTester> histogram_tester(new HistogramTester()); |
| helper()->LaunchAppForNewNote(profile()); |
| ASSERT_EQ(0u, file_system_->handledUrlRequests().size()); |
| |
| histogram_tester->ExpectUniqueSample( |
| NoteTakingHelper::kPreferredLaunchResultHistogramName, |
| static_cast<int>(LaunchResult::NO_APP_SPECIFIED), 1); |
| histogram_tester->ExpectUniqueSample( |
| NoteTakingHelper::kDefaultLaunchResultHistogramName, |
| static_cast<int>(LaunchResult::NO_INTERNAL_DISPLAY_FOUND), 1); |
| } |
| |
| TEST_F(NoteTakingHelperTest, LaunchAndroidApp) { |
| // Since now launching Android apps require window info, this step is needed |
| // to make display info available. |
| ASSERT_TRUE(Shell::Get()); |
| display::test::DisplayManagerTestApi(Shell::Get()->display_manager()) |
| .SetFirstDisplayAsInternalDisplay(); |
| |
| const std::string kPackage1 = "org.chromium.package1"; |
| std::vector<IntentHandlerInfoPtr> handlers; |
| handlers.emplace_back(CreateIntentHandlerInfo("App 1", kPackage1)); |
| intent_helper_.SetIntentHandlers(NoteTakingHelper::kIntentAction, |
| std::move(handlers)); |
| |
| Init(ENABLE_PALETTE | ENABLE_PLAY_STORE); |
| base::RunLoop().RunUntilIdle(); |
| ASSERT_TRUE(helper()->IsAppAvailable(profile())); |
| |
| // The installed app should be launched. |
| std::unique_ptr<HistogramTester> histogram_tester(new HistogramTester()); |
| helper()->LaunchAppForNewNote(profile()); |
| ASSERT_EQ(1u, file_system_->handledUrlRequests().size()); |
| EXPECT_EQ(arc::mojom::ActionType::CREATE_NOTE, |
| file_system_->handledUrlRequests().at(0)->action_type); |
| EXPECT_EQ( |
| kPackage1, |
| file_system_->handledUrlRequests().at(0)->activity_name->package_name); |
| EXPECT_EQ( |
| std::string(), |
| file_system_->handledUrlRequests().at(0)->activity_name->activity_name); |
| ASSERT_EQ(0u, file_system_->handledUrlRequests().at(0)->urls.size()); |
| |
| histogram_tester->ExpectUniqueSample( |
| NoteTakingHelper::kPreferredLaunchResultHistogramName, |
| static_cast<int>(LaunchResult::NO_APP_SPECIFIED), 1); |
| histogram_tester->ExpectUniqueSample( |
| NoteTakingHelper::kDefaultLaunchResultHistogramName, |
| static_cast<int>(LaunchResult::ANDROID_SUCCESS), 1); |
| |
| // Install a second app and set it as the preferred app. |
| const std::string kPackage2 = "org.chromium.package2"; |
| handlers.emplace_back(CreateIntentHandlerInfo("App 1", kPackage1)); |
| handlers.emplace_back(CreateIntentHandlerInfo("App 2", kPackage2)); |
| intent_helper_.SetIntentHandlers(NoteTakingHelper::kIntentAction, |
| std::move(handlers)); |
| helper()->OnIntentFiltersUpdated(std::nullopt); |
| base::RunLoop().RunUntilIdle(); |
| helper()->SetPreferredApp(profile(), kPackage2); |
| |
| // The second app should be launched now. |
| intent_helper_.clear_handled_intents(); |
| file_system_->clear_handled_requests(); |
| histogram_tester = std::make_unique<HistogramTester>(); |
| helper()->LaunchAppForNewNote(profile()); |
| ASSERT_EQ(1u, file_system_->handledUrlRequests().size()); |
| EXPECT_EQ(arc::mojom::ActionType::CREATE_NOTE, |
| file_system_->handledUrlRequests().at(0)->action_type); |
| EXPECT_EQ( |
| kPackage2, |
| file_system_->handledUrlRequests().at(0)->activity_name->package_name); |
| EXPECT_EQ( |
| std::string(), |
| file_system_->handledUrlRequests().at(0)->activity_name->activity_name); |
| ASSERT_EQ(0u, file_system_->handledUrlRequests().at(0)->urls.size()); |
| |
| histogram_tester->ExpectUniqueSample( |
| NoteTakingHelper::kPreferredLaunchResultHistogramName, |
| static_cast<int>(LaunchResult::ANDROID_SUCCESS), 1); |
| histogram_tester->ExpectTotalCount( |
| NoteTakingHelper::kDefaultLaunchResultHistogramName, 0); |
| } |
| |
| TEST_F(NoteTakingHelperTest, NoAppsAvailable) { |
| Init(ENABLE_PALETTE | ENABLE_PLAY_STORE); |
| |
| // When no note-taking apps are installed, the histograms should just be |
| // updated. |
| HistogramTester histogram_tester; |
| helper()->LaunchAppForNewNote(profile()); |
| histogram_tester.ExpectUniqueSample( |
| NoteTakingHelper::kPreferredLaunchResultHistogramName, |
| static_cast<int>(LaunchResult::NO_APP_SPECIFIED), 1); |
| histogram_tester.ExpectUniqueSample( |
| NoteTakingHelper::kDefaultLaunchResultHistogramName, |
| static_cast<int>(LaunchResult::NO_APPS_AVAILABLE), 1); |
| } |
| |
| TEST_F(NoteTakingHelperTest, NotifyObserverAboutAndroidApps) { |
| Init(ENABLE_PALETTE | ENABLE_PLAY_STORE); |
| TestObserver observer; |
| |
| // Let the app-fetching callback run and check that the observer is notified. |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_EQ(1, observer.num_updates()); |
| |
| // Disabling and enabling Play Store should also notify the observer (and |
| // enabling should request apps again). |
| profile()->GetPrefs()->SetBoolean(arc::prefs::kArcEnabled, false); |
| EXPECT_EQ(2, observer.num_updates()); |
| profile()->GetPrefs()->SetBoolean(arc::prefs::kArcEnabled, true); |
| EXPECT_EQ(3, observer.num_updates()); |
| // Run ARC data removing operation. |
| base::RunLoop().RunUntilIdle(); |
| |
| // Update intent filters and check that the observer is notified again after |
| // apps are received. |
| helper()->OnIntentFiltersUpdated(std::nullopt); |
| EXPECT_EQ(3, observer.num_updates()); |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_EQ(4, observer.num_updates()); |
| } |
| |
| TEST_F(NoteTakingHelperTest, NotifyObserverAboutChromeApps) { |
| Init(ENABLE_PALETTE); |
| TestObserver observer; |
| ASSERT_EQ(0, observer.num_updates()); |
| |
| // Notify that the prod Keep app was installed for the initial profile. Chrome |
| // extensions are queried dynamically when GetAvailableApps() is called, so we |
| // don't need to actually install it. |
| scoped_refptr<const extensions::Extension> keep_extension = |
| CreateExtension(kProdKeepExtensionId, "Keep"); |
| InstallExtension(keep_extension.get(), profile()); |
| EXPECT_EQ(1, observer.num_updates()); |
| |
| // Unloading the extension should also trigger a notification. |
| UninstallExtension(keep_extension.get(), profile()); |
| EXPECT_EQ(2, observer.num_updates()); |
| |
| // Non-note-taking apps shouldn't trigger notifications. |
| scoped_refptr<const extensions::Extension> other_extension = |
| CreateExtension(crx_file::id_util::GenerateId("a"), "Some Other App"); |
| InstallExtension(other_extension.get(), profile()); |
| EXPECT_EQ(2, observer.num_updates()); |
| UninstallExtension(other_extension.get(), profile()); |
| EXPECT_EQ(2, observer.num_updates()); |
| |
| // Add a second profile and check that it triggers notifications too. |
| observer.reset_num_updates(); |
| TestingProfile* second_profile = CreateAndInitSecondaryProfile(); |
| DCHECK(ash::ProfileHelper::IsPrimaryProfile(profile())); |
| DCHECK(!ash::ProfileHelper::IsPrimaryProfile(second_profile)); |
| |
| scoped_refptr<const extensions::Extension> second_keep_extension = |
| CreateExtension(kProdKeepExtensionId, "Keep"); |
| EXPECT_EQ(0, observer.num_updates()); |
| InstallExtension(second_keep_extension.get(), second_profile); |
| EXPECT_EQ(1, observer.num_updates()); |
| UninstallExtension(second_keep_extension.get(), second_profile); |
| EXPECT_EQ(2, observer.num_updates()); |
| } |
| |
| TEST_F(NoteTakingHelperTest, NotifyObserverAboutPreferredAppChanges) { |
| Init(ENABLE_PALETTE); |
| TestObserver observer; |
| |
| scoped_refptr<const extensions::Extension> prod_keep_extension = |
| CreateExtension(kProdKeepExtensionId, "Keep"); |
| InstallExtension(prod_keep_extension.get(), profile()); |
| |
| scoped_refptr<const extensions::Extension> dev_keep_extension = |
| CreateExtension(kDevKeepExtensionId, "Keep"); |
| InstallExtension(dev_keep_extension.get(), profile()); |
| |
| ASSERT_TRUE(observer.preferred_app_updates().empty()); |
| |
| // Observers should be notified when preferred app is set. |
| helper()->SetPreferredApp(profile(), prod_keep_extension->id()); |
| EXPECT_EQ(std::vector<raw_ptr<Profile>>{profile()}, |
| observer.preferred_app_updates()); |
| observer.clear_preferred_app_updates(); |
| |
| // If the preferred app is not changed, observers should not be notified. |
| helper()->SetPreferredApp(profile(), prod_keep_extension->id()); |
| EXPECT_TRUE(observer.preferred_app_updates().empty()); |
| |
| // Observers should be notified when preferred app is changed. |
| helper()->SetPreferredApp(profile(), dev_keep_extension->id()); |
| EXPECT_EQ(std::vector<raw_ptr<Profile>>{profile()}, |
| observer.preferred_app_updates()); |
| observer.clear_preferred_app_updates(); |
| |
| // Observers should be notified when preferred app is cleared. |
| helper()->SetPreferredApp(profile(), ""); |
| EXPECT_EQ(std::vector<raw_ptr<Profile>>{profile()}, |
| observer.preferred_app_updates()); |
| observer.clear_preferred_app_updates(); |
| |
| // No change to preferred app. |
| helper()->SetPreferredApp(profile(), ""); |
| EXPECT_TRUE(observer.preferred_app_updates().empty()); |
| |
| // Initialize secondary profile with a test app. |
| TestingProfile* second_profile = CreateAndInitSecondaryProfile(); |
| scoped_refptr<const extensions::Extension> |
| second_profile_prod_keep_extension = |
| CreateExtension(kProdKeepExtensionId, "Keep"); |
| InstallExtension(second_profile_prod_keep_extension.get(), second_profile); |
| |
| // Verify that observers are called with the scondary profile if the secondary |
| // profile preferred app changes. |
| helper()->SetPreferredApp(second_profile, |
| second_profile_prod_keep_extension->id()); |
| EXPECT_EQ(std::vector<raw_ptr<Profile>>{second_profile}, |
| observer.preferred_app_updates()); |
| observer.clear_preferred_app_updates(); |
| |
| // Clearing preferred app in secondary ptofile should fire observers with the |
| // secondary profile. |
| helper()->SetPreferredApp(second_profile, ""); |
| EXPECT_EQ(std::vector<raw_ptr<Profile>>{second_profile}, |
| observer.preferred_app_updates()); |
| observer.clear_preferred_app_updates(); |
| } |
| |
| TEST_F(NoteTakingHelperTest, NoteTakingControllerClient) { |
| Init(ENABLE_PALETTE); |
| |
| auto has_note_taking_apps = [&]() { |
| auto* client = NoteTakingClient::GetInstance(); |
| return client && client->CanCreateNote(); |
| }; |
| |
| EXPECT_FALSE(has_note_taking_apps()); |
| |
| { |
| SetNoteTakingClientProfile(profile()); |
| EXPECT_FALSE(has_note_taking_apps()); |
| |
| scoped_refptr<const extensions::Extension> extension1 = |
| CreateExtension(kProdKeepExtensionId, kProdKeepAppName); |
| scoped_refptr<const extensions::Extension> extension2 = |
| CreateExtension(kDevKeepExtensionId, kDevKeepAppName); |
| |
| InstallExtension(extension1.get(), profile()); |
| EXPECT_TRUE(has_note_taking_apps()); |
| |
| InstallExtension(extension2.get(), profile()); |
| EXPECT_TRUE(has_note_taking_apps()); |
| |
| UninstallExtension(extension1.get(), profile()); |
| EXPECT_TRUE(has_note_taking_apps()); |
| |
| UninstallExtension(extension2.get(), profile()); |
| EXPECT_FALSE(has_note_taking_apps()); |
| |
| InstallExtension(extension1.get(), profile()); |
| EXPECT_TRUE(has_note_taking_apps()); |
| } |
| |
| { |
| TestingProfile* second_profile = CreateAndInitSecondaryProfile(); |
| |
| SetNoteTakingClientProfile(second_profile); |
| EXPECT_FALSE(has_note_taking_apps()); |
| |
| scoped_refptr<const extensions::Extension> extension1 = |
| CreateExtension(kProdKeepExtensionId, kProdKeepAppName); |
| scoped_refptr<const extensions::Extension> extension2 = |
| CreateExtension(kDevKeepExtensionId, kDevKeepAppName); |
| |
| InstallExtension(extension2.get(), second_profile); |
| EXPECT_TRUE(has_note_taking_apps()); |
| |
| SetNoteTakingClientProfile(profile()); |
| EXPECT_TRUE(has_note_taking_apps()); |
| |
| NoteTakingClient::GetInstance()->CreateNote(); |
| ASSERT_EQ(1u, launched_chrome_apps_.size()); |
| ASSERT_EQ(kProdKeepExtensionId, launched_chrome_apps_[0].id); |
| |
| UninstallExtension(extension2.get(), second_profile); |
| EXPECT_TRUE(has_note_taking_apps()); |
| } |
| } |
| |
| } // namespace ash |