#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;
constexpr char kTestProfileName[] = "test-profile";
constexpr char kSecondProfileName[] = "second-profile";
constexpr GaiaId::Literal kFakeGaia2("fakegaia2");
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);
}
IntentHandlerInfoPtr CreateIntentHandlerInfo(const std::string& name,
const std::string& package) {
IntentHandlerInfoPtr handler = IntentHandlerInfo::New();
handler->name = name;
handler->package_name = package;
return handler;
}
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:
void OnAvailableNoteTakingAppsUpdated() override { num_updates_++; }
void OnPreferredNoteTakingAppUpdated(Profile* profile) override {
preferred_app_updates_.push_back(profile);
}
int num_updates_ = 0;
std::vector<raw_ptr<Profile>> preferred_app_updates_;
};
}
class NoteTakingHelperTest : public BrowserWithTestWindowTest {
public:
NoteTakingHelperTest() {
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);
arc_app_test_.PreProfileSetUp();
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_app_test_.PreProfileTearDown();
}
BrowserWithTestWindowTest::TearDown();
arc_app_test_.PostProfileTearDown();
SessionManagerClient::Shutdown();
ash::ProfileHelper::SetProfileToUserForTestingEnabled(false);
}
protected:
struct ChromeAppLaunchInfo {
extensions::ExtensionId id;
};
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);
}
void Init(uint32_t flags) {
ASSERT_FALSE(initialized_);
initialized_ = true;
profile()->GetPrefs()->SetBoolean(arc::prefs::kArcEnabled,
flags & ENABLE_PLAY_STORE);
arc_app_test_.PostProfileSetUp(profile());
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);
}
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)));
}
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);
}
void InitExtensionService(Profile* profile) {
extensions::TestExtensionSystem* extension_system =
static_cast<extensions::TestExtensionSystem*>(
extensions::ExtensionSystem::Get(profile));
extension_system->CreateExtensionService(
base::CommandLine::ForCurrentProcess(),
base::FilePath() ,
false );
}
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);
}
std::optional<std::string> GetDefaultProfileName() override {
return kTestProfileName;
}
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 ,
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",
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();
}
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_;
raw_ptr<sync_preferences::TestingPrefServiceSyncable, DanglingUntriaged>
profile_prefs_ = nullptr;
private:
void LaunchChromeApp(content::BrowserContext* passed_context,
const extensions::Extension* extension) {
EXPECT_EQ(profile(), passed_context);
launched_chrome_apps_.push_back(ChromeAppLaunchInfo{extension->id()});
}
bool initialized_ = false;
ArcAppTest arc_app_test_{ArcAppTest::UserManagerMode::kDoNothing};
std::unique_ptr<arc::FakeIntentHelperHost> intent_helper_host_;
base::test::ScopedFeatureList feature_list_;
};
TEST_F(NoteTakingHelperTest, PaletteNotEnabled) {
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);
EXPECT_FALSE(helper()->IsAppAvailable(profile()));
EXPECT_TRUE(helper()->GetAvailableApps(profile()).empty());
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 }}));
scoped_refptr<const extensions::Extension> dev_extension =
CreateExtension(kDevKeepExtensionId, kDevKeepAppName);
InstallExtension(dev_extension.get(), profile());
EXPECT_TRUE(AvailableAppsMatch(
profile(),
{{kDevKeepAppName, kDevKeepExtensionId, false },
{kProdKeepAppName, kProdKeepExtensionId, false }}));
EXPECT_TRUE(helper()->GetPreferredAppId(profile()).empty());
web_app::test::InstallDummyWebApp(profile(), "Web App",
GURL("http://some.url"));
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 },
{kProdKeepAppName, kProdKeepExtensionId, false }}));
EXPECT_TRUE(helper()->GetPreferredAppId(profile()).empty());
helper()->SetPreferredApp(profile(), kProdKeepExtensionId);
EXPECT_TRUE(AvailableAppsMatch(
profile(),
{{kDevKeepAppName, kDevKeepExtensionId, false },
{kProdKeepAppName, kProdKeepExtensionId, true }}));
EXPECT_EQ(helper()->GetPreferredAppId(profile()), kProdKeepExtensionId);
}
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";
app_info->note_taking_new_note_url = GURL("http://some2.url/new-note");
app2_id = web_app::test::InstallWebApp(profile(), std::move(app_info));
}
auto* provider = web_app::WebAppProvider::GetForTest(profile());
EXPECT_EQ(provider->registrar_unsafe().CountUserInstalledApps(), 2);
EXPECT_TRUE(AvailableAppsMatch(
profile(), {{"Web App 2", app2_id, false }}));
}
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";
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");
app_info->lock_screen_start_url =
GURL("http://some2.url/lock-screen-start");
app2_id = web_app::test::InstallWebApp(profile(), std::move(app_info));
}
auto* provider = web_app::WebAppProvider::GetForTest(profile());
EXPECT_EQ(provider->registrar_unsafe().CountUserInstalledApps(), 2);
EXPECT_TRUE(AvailableAppsMatch(
profile(), {{"Web App 1", app1_id, false},
{"Web App 2", app2_id, false}}));
}
class NoteTakingHelperTest_WebLockScreenApiEnabled
: public NoteTakingHelperTest {
base::test::ScopedFeatureList features_{::features::kWebLockScreenApi};
};
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";
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");
app_info->lock_screen_start_url =
GURL("http://some2.url/lock-screen-start");
app2_id = web_app::test::InstallWebApp(profile(), std::move(app_info));
}
auto* provider = web_app::WebAppProvider::GetForTest(profile());
EXPECT_EQ(provider->registrar_unsafe().CountUserInstalledApps(), 2);
EXPECT_TRUE(AvailableAppsMatch(
profile(), {{"Web App 1", app1_id, false},
{"Web App 2", app2_id, false}}));
}
TEST_F(NoteTakingHelperTest, LaunchChromeApp) {
Init(ENABLE_PALETTE);
scoped_refptr<const extensions::Extension> extension =
CreateExtension(kProdKeepExtensionId, "Keep");
InstallExtension(extension.get(), profile());
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());
{
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);
}
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);
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);
UninstallExtension(dev_extension.get(), profile());
launched_chrome_apps_.clear();
histogram_tester = std::make_unique<HistogramTester>();
helper()->LaunchAppForNewNote(profile());
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());
profile()->GetPrefs()->SetBoolean(arc::prefs::kArcEnabled, true);
EXPECT_TRUE(helper()->play_store_enabled());
EXPECT_FALSE(helper()->android_apps_received());
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());
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 ,
TestingProfile::TestingFactories());
EXPECT_TRUE(helper()->play_store_enabled());
EXPECT_FALSE(helper()->android_apps_received());
EXPECT_EQ(1, observer.num_updates());
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) {
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));
Init(ENABLE_PALETTE | ENABLE_PLAY_STORE);
EXPECT_TRUE(helper()->play_store_enabled());
EXPECT_FALSE(helper()->android_apps_received());
EXPECT_TRUE(helper()->GetAvailableApps(profile()).empty());
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 },
{kName2, kPackage2, false }}));
helper()->SetPreferredApp(profile(), kPackage1);
EXPECT_TRUE(AvailableAppsMatch(profile(),
{{kName1, kPackage1, true },
{kName2, kPackage2, false }}));
EXPECT_TRUE(helper()->GetPreferredAppId(profile()).empty());
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) {
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()));
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) {
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()));
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);
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);
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);
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;
base::RunLoop().RunUntilIdle();
EXPECT_EQ(1, observer.num_updates());
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());
base::RunLoop().RunUntilIdle();
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());
scoped_refptr<const extensions::Extension> keep_extension =
CreateExtension(kProdKeepExtensionId, "Keep");
InstallExtension(keep_extension.get(), profile());
EXPECT_EQ(1, observer.num_updates());
UninstallExtension(keep_extension.get(), profile());
EXPECT_EQ(2, observer.num_updates());
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());
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());
helper()->SetPreferredApp(profile(), prod_keep_extension->id());
EXPECT_EQ(std::vector<raw_ptr<Profile>>{profile()},
observer.preferred_app_updates());
observer.clear_preferred_app_updates();
helper()->SetPreferredApp(profile(), prod_keep_extension->id());
EXPECT_TRUE(observer.preferred_app_updates().empty());
helper()->SetPreferredApp(profile(), dev_keep_extension->id());
EXPECT_EQ(std::vector<raw_ptr<Profile>>{profile()},
observer.preferred_app_updates());
observer.clear_preferred_app_updates();
helper()->SetPreferredApp(profile(), "");
EXPECT_EQ(std::vector<raw_ptr<Profile>>{profile()},
observer.preferred_app_updates());
observer.clear_preferred_app_updates();
helper()->SetPreferredApp(profile(), "");
EXPECT_TRUE(observer.preferred_app_updates().empty());
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);
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();
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());
}
}
}