blob: 217ec7733c8bf552067a4305edc5ff325b94c17a [file] [log] [blame]
// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/chromeos/note_taking_helper.h"
#include <utility>
#include "ash/common/ash_switches.h"
#include "base/bind.h"
#include "base/command_line.h"
#include "base/files/file_path.h"
#include "base/macros.h"
#include "base/run_loop.h"
#include "base/strings/stringprintf.h"
#include "base/test/histogram_tester.h"
#include "chrome/browser/chrome_notification_types.h"
#include "chrome/browser/chromeos/file_manager/path_util.h"
#include "chrome/browser/extensions/extension_service.h"
#include "chrome/browser/extensions/test_extension_system.h"
#include "chrome/browser/ui/app_list/arc/arc_app_test.h"
#include "chrome/common/pref_names.h"
#include "chrome/test/base/browser_with_test_window_test.h"
#include "chrome/test/base/testing_browser_process.h"
#include "chrome/test/base/testing_profile_manager.h"
#include "chromeos/dbus/dbus_thread_manager.h"
#include "chromeos/dbus/fake_session_manager_client.h"
#include "components/arc/arc_bridge_service.h"
#include "components/arc/arc_service_manager.h"
#include "components/arc/arc_util.h"
#include "components/arc/common/intent_helper.mojom.h"
#include "components/arc/test/fake_intent_helper_instance.h"
#include "components/crx_file/id_util.h"
#include "content/public/browser/notification_service.h"
#include "content/public/browser/notification_source.h"
#include "extensions/common/api/app_runtime.h"
#include "extensions/common/extension_builder.h"
#include "extensions/common/extension_id.h"
#include "extensions/common/value_builder.h"
#include "url/gurl.h"
namespace app_runtime = extensions::api::app_runtime;
using arc::mojom::IntentHandlerInfo;
using arc::mojom::IntentHandlerInfoPtr;
using base::HistogramTester;
using HandledIntent = arc::FakeIntentHelperInstance::HandledIntent;
namespace chromeos {
using LaunchResult = NoteTakingHelper::LaunchResult;
namespace {
// Name of default profile.
const char kTestProfileName[] = "test-profile";
// Helper functions returning strings that can be used to compare information
// about available note-taking apps.
std::string GetAppString(const std::string& id,
const std::string& name,
bool preferred) {
return base::StringPrintf("%s %s %d", id.c_str(), name.c_str(), preferred);
}
std::string GetAppString(const NoteTakingAppInfo& info) {
return GetAppString(info.app_id, info.name, info.preferred);
}
// Helper functions returning strings that can be used to compare launched
// intents.
std::string GetIntentString(const std::string& package,
const std::string& clip_data_uri) {
return base::StringPrintf(
"%s %s", package.c_str(),
clip_data_uri.empty() ? "[unset]" : clip_data_uri.c_str());
}
std::string GetIntentString(const HandledIntent& intent) {
EXPECT_EQ(NoteTakingHelper::kIntentAction, intent.intent->action);
return GetIntentString(
intent.activity->package_name,
(intent.intent->clip_data_uri ? *intent.intent->clip_data_uri : ""));
}
// 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;
}
// Converts a filesystem path to an ARC URL.
std::string GetArcUrl(const base::FilePath& path) {
GURL url;
EXPECT_TRUE(file_manager::util::ConvertPathToArcUrl(path, &url));
return url.spec();
}
// Implementation of NoteTakingHelper::Observer for testing.
class TestObserver : public NoteTakingHelper::Observer {
public:
TestObserver() { NoteTakingHelper::Get()->AddObserver(this); }
~TestObserver() override { NoteTakingHelper::Get()->RemoveObserver(this); }
int num_updates() const { return num_updates_; }
void reset_num_updates() { num_updates_ = 0; }
private:
// NoteTakingHelper::Observer:
void OnAvailableNoteTakingAppsUpdated() override { num_updates_++; }
// Number of times that OnAvailableNoteTakingAppsUpdated() has been called.
int num_updates_ = 0;
DISALLOW_COPY_AND_ASSIGN(TestObserver);
};
} // namespace
class NoteTakingHelperTest : public BrowserWithTestWindowTest,
public ::testing::WithParamInterface<bool> {
public:
NoteTakingHelperTest() = default;
~NoteTakingHelperTest() override = default;
void SetUp() override {
if (GetParam())
arc::SetArcAlwaysStartForTesting();
// This is needed to avoid log spam due to ArcSessionManager's
// RemoveArcData() calls failing.
if (DBusThreadManager::IsInitialized())
DBusThreadManager::Shutdown();
session_manager_client_ = new FakeSessionManagerClient();
session_manager_client_->set_arc_available(true);
DBusThreadManager::GetSetterForTesting()->SetSessionManagerClient(
std::unique_ptr<SessionManagerClient>(session_manager_client_));
profile_manager_.reset(
new TestingProfileManager(TestingBrowserProcess::GetGlobal()));
ASSERT_TRUE(profile_manager_->SetUp());
BrowserWithTestWindowTest::SetUp();
InitExtensionService(profile());
}
void TearDown() override {
if (initialized_) {
NoteTakingHelper::Shutdown();
arc_test_.TearDown();
}
extensions::ExtensionSystem::Get(profile())->Shutdown();
BrowserWithTestWindowTest::TearDown();
DBusThreadManager::Shutdown();
}
protected:
// Information about a Chrome app passed to LaunchChromeApp().
struct ChromeAppLaunchInfo {
extensions::ExtensionId id;
base::FilePath path;
};
// 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(); }
// Initializes ARC and NoteTakingHelper. |flags| contains OR-ed together
// InitFlags values.
void Init(uint32_t flags) {
ASSERT_FALSE(initialized_);
initialized_ = true;
profile()->GetPrefs()->SetBoolean(prefs::kArcEnabled,
flags & ENABLE_PLAY_STORE);
arc_test_.SetUp(profile());
arc::ArcServiceManager::Get()
->arc_bridge_service()
->intent_helper()
->SetInstance(&intent_helper_);
if (flags & ENABLE_PALETTE) {
base::CommandLine::ForCurrentProcess()->AppendSwitch(
ash::switches::kAshForceEnablePalette);
}
// TODO(derat): Sigh, something in ArcAppTest appears to be re-enabling ARC.
profile()->GetPrefs()->SetBoolean(prefs::kArcEnabled,
flags & ENABLE_PLAY_STORE);
NoteTakingHelper::Initialize();
NoteTakingHelper::Get()->set_launch_chrome_app_callback_for_test(base::Bind(
&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, nullptr);
}
scoped_refptr<const extensions::Extension> CreateExtension(
const extensions::ExtensionId& id,
const std::string& name,
std::unique_ptr<base::Value> action_handlers) {
std::unique_ptr<base::DictionaryValue> manifest =
extensions::DictionaryBuilder()
.Set("name", name)
.Set("version", "1.0")
.Set("manifest_version", 2)
.Set("app",
extensions::DictionaryBuilder()
.Set("background",
extensions::DictionaryBuilder()
.Set("scripts", extensions::ListBuilder()
.Append("background.js")
.Build())
.Build())
.Build())
.Build();
if (action_handlers)
manifest->Set("action_handlers", std::move(action_handlers));
return extensions::ExtensionBuilder()
.SetManifest(std::move(manifest))
.SetID(id)
.Build();
}
// 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::ExtensionSystem::Get(profile)
->extension_service()
->AddExtension(extension);
}
void UninstallExtension(const extensions::Extension* extension,
Profile* profile) {
extensions::ExtensionSystem::Get(profile)
->extension_service()
->UnloadExtension(extension->id(),
extensions::UnloadedExtensionInfo::REASON_UNINSTALL);
}
// BrowserWithTestWindowTest:
TestingProfile* CreateProfile() override {
// Ensure that the profile created by BrowserWithTestWindowTest is
// registered with |profile_manager_|.
return profile_manager_->CreateTestingProfile(kTestProfileName);
}
void DestroyProfile(TestingProfile* profile) override {
return profile_manager_->DeleteTestingProfile(kTestProfileName);
}
// Info about launched Chrome apps, in the order they were launched.
std::vector<ChromeAppLaunchInfo> launched_chrome_apps_;
arc::FakeIntentHelperInstance intent_helper_;
std::unique_ptr<TestingProfileManager> profile_manager_;
private:
// Callback registered with the helper to record Chrome app launch requests.
void LaunchChromeApp(Profile* passed_profile,
const extensions::Extension* extension,
std::unique_ptr<app_runtime::ActionData> action_data,
const base::FilePath& path) {
EXPECT_EQ(profile(), passed_profile);
EXPECT_EQ(app_runtime::ActionType::ACTION_TYPE_NEW_NOTE,
action_data->action_type);
launched_chrome_apps_.push_back(ChromeAppLaunchInfo{extension->id(), path});
}
// Has Init() been called?
bool initialized_ = false;
FakeSessionManagerClient* session_manager_client_ = nullptr; // Not owned.
ArcAppTest arc_test_;
DISALLOW_COPY_AND_ASSIGN(NoteTakingHelperTest);
};
INSTANTIATE_TEST_CASE_P(, NoteTakingHelperTest, ::testing::Bool());
TEST_P(NoteTakingHelperTest, PaletteNotEnabled) {
// Without the palette enabled, IsAppAvailable() should return false.
Init(0);
auto extension =
CreateExtension(NoteTakingHelper::kProdKeepExtensionId, "Keep");
InstallExtension(extension.get(), profile());
EXPECT_FALSE(helper()->IsAppAvailable(profile()));
}
TEST_P(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.
const std::string kProdName = "Google Keep [prod]";
auto prod_extension =
CreateExtension(NoteTakingHelper::kProdKeepExtensionId, kProdName);
InstallExtension(prod_extension.get(), profile());
EXPECT_TRUE(helper()->IsAppAvailable(profile()));
std::vector<NoteTakingAppInfo> apps = helper()->GetAvailableApps(profile());
ASSERT_EQ(1u, apps.size());
EXPECT_EQ(
GetAppString(NoteTakingHelper::kProdKeepExtensionId, kProdName, false),
GetAppString(apps[0]));
// If the dev version is also installed, it should be listed before the prod
// version.
const std::string kDevName = "Google Keep [dev]";
auto dev_extension =
CreateExtension(NoteTakingHelper::kDevKeepExtensionId, kDevName);
InstallExtension(dev_extension.get(), profile());
apps = helper()->GetAvailableApps(profile());
ASSERT_EQ(2u, apps.size());
EXPECT_EQ(
GetAppString(NoteTakingHelper::kDevKeepExtensionId, kDevName, false),
GetAppString(apps[0]));
EXPECT_EQ(
GetAppString(NoteTakingHelper::kProdKeepExtensionId, kProdName, false),
GetAppString(apps[1]));
// Now install a random extension and check that it's ignored.
const extensions::ExtensionId kOtherId = crx_file::id_util::GenerateId("a");
const std::string kOtherName = "Some Other App";
auto other_extension = CreateExtension(kOtherId, kOtherName);
InstallExtension(other_extension.get(), profile());
apps = helper()->GetAvailableApps(profile());
ASSERT_EQ(2u, apps.size());
EXPECT_EQ(
GetAppString(NoteTakingHelper::kDevKeepExtensionId, kDevName, false),
GetAppString(apps[0]));
EXPECT_EQ(
GetAppString(NoteTakingHelper::kProdKeepExtensionId, kProdName, false),
GetAppString(apps[1]));
// Mark the prod version as preferred.
helper()->SetPreferredApp(profile(), NoteTakingHelper::kProdKeepExtensionId);
apps = helper()->GetAvailableApps(profile());
ASSERT_EQ(2u, apps.size());
EXPECT_EQ(
GetAppString(NoteTakingHelper::kDevKeepExtensionId, kDevName, false),
GetAppString(apps[0]));
EXPECT_EQ(
GetAppString(NoteTakingHelper::kProdKeepExtensionId, kProdName, true),
GetAppString(apps[1]));
}
// Verify the note helper detects apps with "new_note" "action_handler" manifest
// entries.
TEST_P(NoteTakingHelperTest, CustomChromeApps) {
Init(ENABLE_PALETTE);
const extensions::ExtensionId kNewNoteId = crx_file::id_util::GenerateId("a");
const extensions::ExtensionId kEmptyArrayId =
crx_file::id_util::GenerateId("b");
const extensions::ExtensionId kEmptyId = crx_file::id_util::GenerateId("c");
const std::string kName = "Some App";
// "action_handlers": ["new_note"]
auto has_new_note = CreateExtension(
kNewNoteId, kName,
extensions::ListBuilder()
.Append(app_runtime::ToString(app_runtime::ACTION_TYPE_NEW_NOTE))
.Build());
InstallExtension(has_new_note.get(), profile());
// "action_handlers": []
auto empty_array =
CreateExtension(kEmptyArrayId, kName, extensions::ListBuilder().Build());
InstallExtension(empty_array.get(), profile());
// (no action handler entry)
auto none = CreateExtension(kEmptyId, kName);
InstallExtension(none.get(), profile());
// Only the "new_note" extension is returned from GetAvailableApps.
std::vector<NoteTakingAppInfo> apps = helper()->GetAvailableApps(profile());
ASSERT_EQ(1u, apps.size());
EXPECT_EQ(GetAppString(kNewNoteId, kName, false), GetAppString(apps[0]));
}
TEST_P(NoteTakingHelperTest, WhitelistedAndCustomAppsShowOnlyOnce) {
Init(ENABLE_PALETTE);
auto extension = CreateExtension(
NoteTakingHelper::kProdKeepExtensionId, "Keep",
extensions::ListBuilder()
.Append(app_runtime::ToString(app_runtime::ACTION_TYPE_NEW_NOTE))
.Build());
InstallExtension(extension.get(), profile());
std::vector<NoteTakingAppInfo> apps = helper()->GetAvailableApps(profile());
ASSERT_EQ(1u, apps.size());
EXPECT_EQ(GetAppString(NoteTakingHelper::kProdKeepExtensionId, "Keep", false),
GetAppString(apps[0]));
}
TEST_P(NoteTakingHelperTest, LaunchChromeApp) {
Init(ENABLE_PALETTE);
auto extension =
CreateExtension(NoteTakingHelper::kProdKeepExtensionId, "Keep");
InstallExtension(extension.get(), profile());
// Check the Chrome app is launched with the correct parameters.
HistogramTester histogram_tester;
const base::FilePath kPath("/foo/bar/photo.jpg");
helper()->LaunchAppForNewNote(profile(), kPath);
ASSERT_EQ(1u, launched_chrome_apps_.size());
EXPECT_EQ(NoteTakingHelper::kProdKeepExtensionId,
launched_chrome_apps_[0].id);
EXPECT_EQ(kPath, launched_chrome_apps_[0].path);
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_P(NoteTakingHelperTest, FallBackIfPreferredAppUnavailable) {
Init(ENABLE_PALETTE);
auto prod_extension =
CreateExtension(NoteTakingHelper::kProdKeepExtensionId, "prod");
InstallExtension(prod_extension.get(), profile());
auto dev_extension =
CreateExtension(NoteTakingHelper::kDevKeepExtensionId, "dev");
InstallExtension(dev_extension.get(), profile());
// Set the prod app as preferred and check that it's launched.
std::unique_ptr<HistogramTester> histogram_tester(new HistogramTester());
helper()->SetPreferredApp(profile(), NoteTakingHelper::kProdKeepExtensionId);
helper()->LaunchAppForNewNote(profile(), base::FilePath());
ASSERT_EQ(1u, launched_chrome_apps_.size());
ASSERT_EQ(NoteTakingHelper::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.reset(new HistogramTester());
helper()->LaunchAppForNewNote(profile(), base::FilePath());
ASSERT_EQ(1u, launched_chrome_apps_.size());
EXPECT_EQ(NoteTakingHelper::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);
}
TEST_P(NoteTakingHelperTest, PlayStoreInitiallyDisabled) {
Init(ENABLE_PALETTE);
EXPECT_FALSE(helper()->play_store_enabled());
EXPECT_FALSE(helper()->android_apps_received());
// TODO(victorhsieh): Implement opt-in.
if (arc::ShouldArcAlwaysStart())
return;
// When Play Store is enabled, the helper's members should be updated
// accordingly.
profile()->GetPrefs()->SetBoolean(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();
base::RunLoop().RunUntilIdle();
EXPECT_TRUE(helper()->play_store_enabled());
EXPECT_TRUE(helper()->android_apps_received());
}
TEST_P(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()));
std::vector<NoteTakingAppInfo> apps = helper()->GetAvailableApps(profile());
ASSERT_EQ(2u, apps.size());
EXPECT_EQ(GetAppString(kPackage1, kName1, false), GetAppString(apps[0]));
EXPECT_EQ(GetAppString(kPackage2, kName2, false), GetAppString(apps[1]));
// TODO(victorhsieh): Opt-out on Persistent ARC is special. Skip until
// implemented.
if (arc::ShouldArcAlwaysStart())
return;
// Disable Play Store and check that the apps are no longer returned.
profile()->GetPrefs()->SetBoolean(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_P(NoteTakingHelperTest, LaunchAndroidApp) {
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(), base::FilePath());
ASSERT_EQ(1u, intent_helper_.handled_intents().size());
EXPECT_EQ(GetIntentString(kPackage1, ""),
GetIntentString(intent_helper_.handled_intents()[0]));
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();
base::RunLoop().RunUntilIdle();
helper()->SetPreferredApp(profile(), kPackage2);
// The second app should be launched now.
intent_helper_.clear_handled_intents();
histogram_tester.reset(new HistogramTester());
helper()->LaunchAppForNewNote(profile(), base::FilePath());
ASSERT_EQ(1u, intent_helper_.handled_intents().size());
EXPECT_EQ(GetIntentString(kPackage2, ""),
GetIntentString(intent_helper_.handled_intents()[0]));
histogram_tester->ExpectUniqueSample(
NoteTakingHelper::kPreferredLaunchResultHistogramName,
static_cast<int>(LaunchResult::ANDROID_SUCCESS), 1);
histogram_tester->ExpectTotalCount(
NoteTakingHelper::kDefaultLaunchResultHistogramName, 0);
}
TEST_P(NoteTakingHelperTest, LaunchAndroidAppWithPath) {
const std::string kPackage = "org.chromium.package";
std::vector<IntentHandlerInfoPtr> handlers;
handlers.emplace_back(CreateIntentHandlerInfo("App", kPackage));
intent_helper_.SetIntentHandlers(NoteTakingHelper::kIntentAction,
std::move(handlers));
Init(ENABLE_PALETTE | ENABLE_PLAY_STORE);
base::RunLoop().RunUntilIdle();
ASSERT_TRUE(helper()->IsAppAvailable(profile()));
const base::FilePath kDownloadedPath(
file_manager::util::GetDownloadsFolderForProfile(profile()).Append(
"image.jpg"));
helper()->LaunchAppForNewNote(profile(), kDownloadedPath);
ASSERT_EQ(1u, intent_helper_.handled_intents().size());
EXPECT_EQ(GetIntentString(kPackage, GetArcUrl(kDownloadedPath)),
GetIntentString(intent_helper_.handled_intents()[0]));
const base::FilePath kRemovablePath =
base::FilePath(file_manager::util::kRemovableMediaPath)
.Append("image.jpg");
intent_helper_.clear_handled_intents();
helper()->LaunchAppForNewNote(profile(), kRemovablePath);
ASSERT_EQ(1u, intent_helper_.handled_intents().size());
EXPECT_EQ(GetIntentString(kPackage, GetArcUrl(kRemovablePath)),
GetIntentString(intent_helper_.handled_intents()[0]));
// When a path that isn't accessible to ARC is passed, the request should be
// dropped.
HistogramTester histogram_tester;
intent_helper_.clear_handled_intents();
helper()->LaunchAppForNewNote(profile(), base::FilePath("/bad/path.jpg"));
EXPECT_TRUE(intent_helper_.handled_intents().empty());
histogram_tester.ExpectUniqueSample(
NoteTakingHelper::kPreferredLaunchResultHistogramName,
static_cast<int>(LaunchResult::NO_APP_SPECIFIED), 1);
histogram_tester.ExpectUniqueSample(
NoteTakingHelper::kDefaultLaunchResultHistogramName,
static_cast<int>(LaunchResult::ANDROID_FAILED_TO_CONVERT_PATH), 1);
}
TEST_P(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(), base::FilePath());
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_P(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());
// TODO(victorhsieh): Opt-out on Persistent ARC is special. Skip until
// implemented.
if (arc::ShouldArcAlwaysStart())
return;
// Disabling and enabling Play Store should also notify the observer (and
// enabling should request apps again).
profile()->GetPrefs()->SetBoolean(prefs::kArcEnabled, false);
EXPECT_EQ(2, observer.num_updates());
profile()->GetPrefs()->SetBoolean(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();
EXPECT_EQ(3, observer.num_updates());
base::RunLoop().RunUntilIdle();
EXPECT_EQ(4, observer.num_updates());
}
TEST_P(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.
auto keep_extension =
CreateExtension(NoteTakingHelper::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-whitelisted apps shouldn't trigger notifications.
auto 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();
const std::string kSecondProfileName = "second-profile";
TestingProfile* second_profile =
profile_manager_->CreateTestingProfile(kSecondProfileName);
InitExtensionService(second_profile);
EXPECT_EQ(0, observer.num_updates());
InstallExtension(keep_extension.get(), second_profile);
EXPECT_EQ(1, observer.num_updates());
UninstallExtension(keep_extension.get(), second_profile);
EXPECT_EQ(2, observer.num_updates());
profile_manager_->DeleteTestingProfile(kSecondProfileName);
}
} // namespace chromeos