blob: 43acefb78f3924037fb9996645ee4d559c7ab969 [file] [log] [blame]
// Copyright (c) 2020 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 <memory>
#include "ash/public/cpp/external_arc/message_center/arc_notification_manager.h"
#include "ash/public/cpp/message_center/arc_notification_manager_delegate.h"
#include "ash/public/cpp/message_center/arc_notifications_host_initializer.h"
#include "base/run_loop.h"
#include "base/strings/utf_string_conversions.h"
#include "base/test/metrics/histogram_tester.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/apps/app_service/arc_apps.h"
#include "chrome/browser/apps/app_service/arc_apps_factory.h"
#include "chrome/browser/apps/platform_apps/app_browsertest_util.h"
#include "chrome/browser/chromeos/arc/arc_util.h"
#include "chrome/browser/chromeos/arc/session/arc_session_manager.h"
#include "chrome/browser/extensions/api/notifications/extension_notification_display_helper.h"
#include "chrome/browser/extensions/api/notifications/extension_notification_display_helper_factory.h"
#include "chrome/browser/extensions/api/notifications/extension_notification_handler.h"
#include "chrome/browser/extensions/api/notifications/notifications_api.h"
#include "chrome/browser/extensions/extension_apitest.h"
#include "chrome/browser/extensions/extension_function_test_utils.h"
#include "chrome/browser/notifications/notification_display_service_tester.h"
#include "chrome/browser/notifications/profile_notification.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/web_applications/test/web_app_browsertest_util.h"
#include "chrome/common/chrome_switches.h"
#include "components/arc/arc_service_manager.h"
#include "components/arc/arc_util.h"
#include "components/arc/session/arc_bridge_service.h"
#include "components/arc/session/connection_holder.h"
#include "components/arc/test/connection_holder_util.h"
#include "components/arc/test/fake_app_instance.h"
#include "content/public/browser/notification_service.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/test_navigation_observer.h"
#include "extensions/browser/api/test/test_api.h"
#include "extensions/browser/notification_types.h"
#include "extensions/common/extension.h"
#include "extensions/test/extension_test_message_listener.h"
#include "mojo/public/cpp/bindings/pending_remote.h"
#include "mojo/public/cpp/bindings/receiver.h"
#include "ui/display/display.h"
#include "ui/message_center/message_center.h"
#include "ui/message_center/public/cpp/notification.h"
#include "ui/message_center/public/cpp/notifier_id.h"
using extensions::Extension;
using extensions::ExtensionNotificationDisplayHelper;
using extensions::ExtensionNotificationDisplayHelperFactory;
namespace {
constexpr char kTestAppName1[] = "Test ARC App1";
constexpr char kTestAppName2[] = "Test ARC App2";
constexpr char kTestAppPackage1[] = "test.arc.app1.package";
constexpr char kTestAppPackage2[] = "test.arc.app2.package";
constexpr char kTestAppActivity1[] = "test.arc.app1.package.activity";
constexpr char kTestAppActivity2[] = "test.arc.app2.package.activity";
std::string GetTestAppId(const std::string& package_name,
const std::string& activity) {
return ArcAppListPrefs::GetAppId(package_name, activity);
}
std::vector<arc::mojom::AppInfoPtr> GetTestAppsList() {
std::vector<arc::mojom::AppInfoPtr> apps;
arc::mojom::AppInfoPtr app(arc::mojom::AppInfo::New());
app->name = kTestAppName1;
app->package_name = kTestAppPackage1;
app->activity = kTestAppActivity1;
app->sticky = false;
apps.push_back(std::move(app));
app = arc::mojom::AppInfo::New();
app->name = kTestAppName2;
app->package_name = kTestAppPackage2;
app->activity = kTestAppActivity2;
app->sticky = false;
apps.push_back(std::move(app));
return apps;
}
apps::mojom::OptionalBool HasBadge(Profile* profile,
const std::string& app_id) {
auto has_badge = apps::mojom::OptionalBool::kUnknown;
apps::AppServiceProxy* proxy =
apps::AppServiceProxyFactory::GetForProfile(profile);
proxy->FlushMojoCallsForTesting();
proxy->AppRegistryCache().ForOneApp(
app_id, [&has_badge](const apps::AppUpdate& update) {
has_badge = update.HasBadge();
});
return has_badge;
}
void RemoveNotification(Profile* profile, const std::string& notification_id) {
const std::string profile_notification_id =
ProfileNotification::GetProfileNotificationId(
notification_id, NotificationUIManager::GetProfileID(profile));
message_center::MessageCenter::Get()->RemoveNotification(
profile_notification_id, true);
}
void UninstallApp(Profile* profile, const std::string& app_id) {
apps::AppServiceProxy* proxy =
apps::AppServiceProxyFactory::GetForProfile(profile);
proxy->UninstallSilently(app_id, apps::mojom::UninstallSource::kUser);
proxy->FlushMojoCallsForTesting();
}
} // namespace
class AppNotificationsExtensionApiTest : public extensions::ExtensionApiTest {
public:
const Extension* LoadExtensionAndWait(const std::string& test_name) {
base::FilePath extdir = test_data_dir_.AppendASCII(test_name);
content::WindowedNotificationObserver page_created(
extensions::NOTIFICATION_EXTENSION_BACKGROUND_PAGE_READY,
content::NotificationService::AllSources());
const extensions::Extension* extension = LoadExtension(extdir);
if (extension) {
page_created.Wait();
}
return extension;
}
const Extension* LoadAppWithWindowState(const std::string& test_name) {
const std::string& create_window_options =
base::StringPrintf("{\"state\":\"normal\"}");
base::FilePath extdir = test_data_dir_.AppendASCII(test_name);
const extensions::Extension* extension = LoadExtension(extdir);
EXPECT_TRUE(extension);
ExtensionTestMessageListener launched_listener("launched", true);
apps::AppServiceProxyFactory::GetForProfile(profile())->Launch(
extension->id(), ui::EF_SHIFT_DOWN,
apps::mojom::LaunchSource::kFromTest, display::kInvalidDisplayId);
EXPECT_TRUE(launched_listener.WaitUntilSatisfied());
launched_listener.Reply(create_window_options);
return extension;
}
ExtensionNotificationDisplayHelper* GetDisplayHelper() {
return ExtensionNotificationDisplayHelperFactory::GetForProfile(profile());
}
protected:
// Returns the notification that's being displayed for |extension|, or nullptr
// when the notification count is not equal to one. It's not safe to rely on
// the Notification pointer after closing the notification, but a copy can be
// made to continue to be able to access the underlying information.
message_center::Notification* GetNotificationForExtension(
const extensions::Extension* extension) {
DCHECK(extension);
std::set<std::string> notifications =
GetDisplayHelper()->GetNotificationIdsForExtension(extension->url());
if (notifications.size() != 1)
return nullptr;
return GetDisplayHelper()->GetByNotificationId(*notifications.begin());
}
};
IN_PROC_BROWSER_TEST_F(AppNotificationsExtensionApiTest,
AddAndRemoveNotification) {
// Load the permission app which should not generate notifications.
const Extension* extension1 =
LoadExtensionAndWait("notifications/api/permission");
ASSERT_TRUE(extension1);
ASSERT_EQ(apps::mojom::OptionalBool::kFalse,
HasBadge(profile(), extension1->id()));
// Load the basic app to generate a notification.
ExtensionTestMessageListener notification_created_listener("created", false);
const Extension* extension2 =
LoadAppWithWindowState("notifications/api/basic_app");
ASSERT_TRUE(extension2);
ASSERT_TRUE(notification_created_listener.WaitUntilSatisfied());
ASSERT_EQ(apps::mojom::OptionalBool::kFalse,
HasBadge(profile(), extension1->id()));
ASSERT_EQ(apps::mojom::OptionalBool::kTrue,
HasBadge(profile(), extension2->id()));
message_center::Notification* notification =
GetNotificationForExtension(extension2);
ASSERT_TRUE(notification);
RemoveNotification(profile(), notification->id());
ASSERT_EQ(apps::mojom::OptionalBool::kFalse,
HasBadge(profile(), extension1->id()));
ASSERT_EQ(apps::mojom::OptionalBool::kFalse,
HasBadge(profile(), extension2->id()));
}
IN_PROC_BROWSER_TEST_F(AppNotificationsExtensionApiTest,
InstallAndUninstallApp) {
// Load the permission app which should not generate notifications.
const Extension* extension1 =
LoadExtensionAndWait("notifications/api/permission");
ASSERT_TRUE(extension1);
ASSERT_EQ(apps::mojom::OptionalBool::kFalse,
HasBadge(profile(), extension1->id()));
// Load the basic app to generate a notification.
ExtensionTestMessageListener notification_created_listener1("created", false);
const Extension* extension2 =
LoadAppWithWindowState("notifications/api/basic_app");
ASSERT_TRUE(extension2);
ASSERT_TRUE(notification_created_listener1.WaitUntilSatisfied());
ASSERT_EQ(apps::mojom::OptionalBool::kFalse,
HasBadge(profile(), extension1->id()));
ASSERT_EQ(apps::mojom::OptionalBool::kTrue,
HasBadge(profile(), extension2->id()));
// Uninstall the basic app.
UninstallApp(profile(), extension2->id());
ASSERT_EQ(apps::mojom::OptionalBool::kFalse,
HasBadge(profile(), extension1->id()));
// Re-load the basic app to generate a notification again.
ExtensionTestMessageListener notification_created_listener2("created", false);
const Extension* extension3 =
LoadAppWithWindowState("notifications/api/basic_app");
ASSERT_TRUE(extension3);
ASSERT_TRUE(notification_created_listener2.WaitUntilSatisfied());
ASSERT_EQ(apps::mojom::OptionalBool::kFalse,
HasBadge(profile(), extension1->id()));
ASSERT_EQ(apps::mojom::OptionalBool::kTrue,
HasBadge(profile(), extension3->id()));
// Remove the notification.
message_center::Notification* notification =
GetNotificationForExtension(extension3);
ASSERT_TRUE(notification);
RemoveNotification(profile(), notification->id());
ASSERT_EQ(apps::mojom::OptionalBool::kFalse,
HasBadge(profile(), extension1->id()));
ASSERT_EQ(apps::mojom::OptionalBool::kFalse,
HasBadge(profile(), extension3->id()));
}
class AppNotificationsWebNotificationTest
: public extensions::PlatformAppBrowserTest {
protected:
AppNotificationsWebNotificationTest() = default;
~AppNotificationsWebNotificationTest() override = default;
// extensions::PlatformAppBrowserTest:
void SetUpOnMainThread() override {
extensions::PlatformAppBrowserTest::SetUpOnMainThread();
https_server_.AddDefaultHandlers(GetChromeTestDataDir());
ASSERT_TRUE(https_server_.Start());
}
void SetUpCommandLine(base::CommandLine* command_line) override {
extensions::PlatformAppBrowserTest::SetUpCommandLine(command_line);
command_line->AppendSwitchASCII(
switches::kDesktopPWAsAttentionBadgingCrOS,
switches::kDesktopPWAsAttentionBadgingCrOSApiAndNotifications);
}
std::string CreateWebApp(const GURL& url, const GURL& scope) const {
auto web_app_info = std::make_unique<WebApplicationInfo>();
web_app_info->start_url = url;
web_app_info->scope = scope;
std::string app_id =
web_app::InstallWebApp(browser()->profile(), std::move(web_app_info));
content::TestNavigationObserver navigation_observer(url);
navigation_observer.StartWatchingNewWebContents();
web_app::LaunchWebAppBrowser(browser()->profile(), app_id);
navigation_observer.WaitForNavigationFinished();
return app_id;
}
std::unique_ptr<message_center::Notification> CreateNotification(
const std::string& notification_id,
const GURL& origin) {
return std::make_unique<message_center::Notification>(
message_center::NOTIFICATION_TYPE_SIMPLE, notification_id,
base::string16(), base::string16(), gfx::Image(),
base::UTF8ToUTF16(origin.host()), origin,
message_center::NotifierId(origin),
message_center::RichNotificationData(), nullptr);
}
void UninstallWebApp(const std::string& app_id) const {
web_app::UninstallWebApp(browser()->profile(), app_id);
apps::AppServiceProxyFactory::GetForProfile(browser()->profile())
->FlushMojoCallsForTesting();
}
GURL GetOrigin() const { return https_server_.GetURL("app.com", "/"); }
GURL GetUrl1() const {
return https_server_.GetURL("app.com", "/ssl/google.html");
}
GURL GetScope1() const { return https_server_.GetURL("app.com", "/ssl/"); }
GURL GetUrl2() const {
return https_server_.GetURL("app.com", "/google/google.html");
}
GURL GetScope2() const { return https_server_.GetURL("app.com", "/google/"); }
GURL GetUrl3() const {
return https_server_.GetURL("app1.com", "/google/google.html");
}
GURL GetScope3() const {
return https_server_.GetURL("app1.com", "/google/");
}
private:
// For mocking a secure site.
net::EmbeddedTestServer https_server_;
};
IN_PROC_BROWSER_TEST_F(AppNotificationsWebNotificationTest,
AddAndRemovePersistentNotification) {
std::string app_id1 = CreateWebApp(GetUrl1(), GetScope1());
std::string app_id2 = CreateWebApp(GetUrl2(), GetScope2());
ASSERT_EQ(apps::mojom::OptionalBool::kFalse, HasBadge(profile(), app_id1));
ASSERT_EQ(apps::mojom::OptionalBool::kFalse, HasBadge(profile(), app_id2));
const GURL origin = GetOrigin();
std::string notification_id = "notification-id1";
auto notification = CreateNotification(notification_id, origin);
auto metadata = std::make_unique<PersistentNotificationMetadata>();
metadata->service_worker_scope = GetScope1();
NotificationDisplayService::GetForProfile(profile())->Display(
NotificationHandler::Type::WEB_PERSISTENT, *notification,
std::move(metadata));
ASSERT_EQ(apps::mojom::OptionalBool::kTrue, HasBadge(profile(), app_id1));
ASSERT_EQ(apps::mojom::OptionalBool::kFalse, HasBadge(profile(), app_id2));
NotificationDisplayService::GetForProfile(profile())->Close(
NotificationHandler::Type::WEB_PERSISTENT, notification_id);
ASSERT_EQ(apps::mojom::OptionalBool::kFalse, HasBadge(profile(), app_id1));
ASSERT_EQ(apps::mojom::OptionalBool::kFalse, HasBadge(profile(), app_id2));
notification_id = "notification-id2";
notification = CreateNotification(notification_id, origin);
metadata = std::make_unique<PersistentNotificationMetadata>();
metadata->service_worker_scope = GetScope2();
NotificationDisplayService::GetForProfile(profile())->Display(
NotificationHandler::Type::WEB_PERSISTENT, *notification,
std::move(metadata));
ASSERT_EQ(apps::mojom::OptionalBool::kFalse, HasBadge(profile(), app_id1));
ASSERT_EQ(apps::mojom::OptionalBool::kTrue, HasBadge(profile(), app_id2));
NotificationDisplayService::GetForProfile(profile())->Close(
NotificationHandler::Type::WEB_PERSISTENT, notification_id);
ASSERT_EQ(apps::mojom::OptionalBool::kFalse, HasBadge(profile(), app_id1));
ASSERT_EQ(apps::mojom::OptionalBool::kFalse, HasBadge(profile(), app_id2));
}
IN_PROC_BROWSER_TEST_F(AppNotificationsWebNotificationTest,
PersistentNotificationWhenInstallAndUninstallApp) {
// Send a notification before installing apps.
const GURL origin = GetOrigin();
std::string notification_id = "notification-id2";
auto notification = CreateNotification(notification_id, origin);
auto metadata = std::make_unique<PersistentNotificationMetadata>();
metadata->service_worker_scope = GetScope2();
NotificationDisplayService::GetForProfile(profile())->Display(
NotificationHandler::Type::WEB_PERSISTENT, *notification,
std::move(metadata));
// Install apps, and verify the notification badge is not set.
std::string app_id1 = CreateWebApp(GetUrl1(), GetScope1());
std::string app_id2 = CreateWebApp(GetUrl2(), GetScope2());
ASSERT_EQ(apps::mojom::OptionalBool::kFalse, HasBadge(profile(), app_id1));
ASSERT_EQ(apps::mojom::OptionalBool::kFalse, HasBadge(profile(), app_id2));
// Remove the notification. It should not affect the notification badge.
NotificationDisplayService::GetForProfile(profile())->Close(
NotificationHandler::Type::WEB_PERSISTENT, notification_id);
ASSERT_EQ(apps::mojom::OptionalBool::kFalse, HasBadge(profile(), app_id1));
ASSERT_EQ(apps::mojom::OptionalBool::kFalse, HasBadge(profile(), app_id2));
// Send a notification for the installed app 2.
notification_id = "notification-id3";
notification = CreateNotification(notification_id, origin);
metadata = std::make_unique<PersistentNotificationMetadata>();
metadata->service_worker_scope = GetScope2();
NotificationDisplayService::GetForProfile(profile())->Display(
NotificationHandler::Type::WEB_PERSISTENT, *notification,
std::move(metadata));
ASSERT_EQ(apps::mojom::OptionalBool::kFalse, HasBadge(profile(), app_id1));
ASSERT_EQ(apps::mojom::OptionalBool::kTrue, HasBadge(profile(), app_id2));
// Uninstall the app 2.
UninstallApp(profile(), app_id2);
// Re-install the app 2.
app_id2 = CreateWebApp(GetUrl2(), GetScope2());
ASSERT_EQ(apps::mojom::OptionalBool::kFalse, HasBadge(profile(), app_id1));
ASSERT_EQ(apps::mojom::OptionalBool::kFalse, HasBadge(profile(), app_id2));
// Remove the notification.
NotificationDisplayService::GetForProfile(profile())->Close(
NotificationHandler::Type::WEB_PERSISTENT, notification_id);
ASSERT_EQ(apps::mojom::OptionalBool::kFalse, HasBadge(profile(), app_id1));
ASSERT_EQ(apps::mojom::OptionalBool::kFalse, HasBadge(profile(), app_id2));
// Resend the notifications for both apps.
std::string notification_id1 = "notification-id4";
notification = CreateNotification(notification_id1, origin);
metadata = std::make_unique<PersistentNotificationMetadata>();
metadata->service_worker_scope = GetScope1();
NotificationDisplayService::GetForProfile(profile())->Display(
NotificationHandler::Type::WEB_PERSISTENT, *notification,
std::move(metadata));
ASSERT_EQ(apps::mojom::OptionalBool::kTrue, HasBadge(profile(), app_id1));
ASSERT_EQ(apps::mojom::OptionalBool::kFalse, HasBadge(profile(), app_id2));
std::string notification_id2 = "notification-id5";
notification = CreateNotification(notification_id2, origin);
metadata = std::make_unique<PersistentNotificationMetadata>();
metadata->service_worker_scope = GetScope2();
NotificationDisplayService::GetForProfile(profile())->Display(
NotificationHandler::Type::WEB_PERSISTENT, *notification,
std::move(metadata));
ASSERT_EQ(apps::mojom::OptionalBool::kTrue, HasBadge(profile(), app_id1));
ASSERT_EQ(apps::mojom::OptionalBool::kTrue, HasBadge(profile(), app_id2));
// Remove notifications.
NotificationDisplayService::GetForProfile(profile())->Close(
NotificationHandler::Type::WEB_PERSISTENT, notification_id1);
ASSERT_EQ(apps::mojom::OptionalBool::kFalse, HasBadge(profile(), app_id1));
ASSERT_EQ(apps::mojom::OptionalBool::kTrue, HasBadge(profile(), app_id2));
NotificationDisplayService::GetForProfile(profile())->Close(
NotificationHandler::Type::WEB_PERSISTENT, notification_id2);
ASSERT_EQ(apps::mojom::OptionalBool::kFalse, HasBadge(profile(), app_id1));
ASSERT_EQ(apps::mojom::OptionalBool::kFalse, HasBadge(profile(), app_id2));
}
IN_PROC_BROWSER_TEST_F(AppNotificationsWebNotificationTest,
AddAndRemoveNonPersistentNotificationForOneApp) {
base::HistogramTester histogram_tester;
const GURL origin = GetOrigin();
std::string app_id1 = CreateWebApp(GetUrl1(), GetScope1());
std::string app_id3 = CreateWebApp(GetUrl3(), GetScope3());
ASSERT_EQ(apps::mojom::OptionalBool::kFalse, HasBadge(profile(), app_id1));
ASSERT_EQ(apps::mojom::OptionalBool::kFalse, HasBadge(profile(), app_id3));
const std::string notification_id = "notification-id";
auto notification = CreateNotification(notification_id, origin);
NotificationDisplayService::GetForProfile(profile())->Display(
NotificationHandler::Type::WEB_NON_PERSISTENT, *notification,
/*metadata=*/nullptr);
ASSERT_EQ(apps::mojom::OptionalBool::kTrue, HasBadge(profile(), app_id1));
ASSERT_EQ(apps::mojom::OptionalBool::kFalse, HasBadge(profile(), app_id3));
histogram_tester.ExpectUniqueSample(
"ChromeOS.Apps.NumberOfAppsForNotification", false, 1);
RemoveNotification(profile(), notification_id);
ASSERT_EQ(apps::mojom::OptionalBool::kFalse, HasBadge(profile(), app_id1));
ASSERT_EQ(apps::mojom::OptionalBool::kFalse, HasBadge(profile(), app_id3));
}
IN_PROC_BROWSER_TEST_F(AppNotificationsWebNotificationTest,
AddAndRemoveNonPersistentNotification) {
base::HistogramTester histogram_tester;
const GURL origin = GetOrigin();
std::string app_id1 = CreateWebApp(GetUrl1(), GetScope1());
std::string app_id2 = CreateWebApp(GetUrl2(), GetScope2());
std::string app_id3 = CreateWebApp(GetUrl3(), GetScope3());
ASSERT_EQ(apps::mojom::OptionalBool::kFalse, HasBadge(profile(), app_id1));
ASSERT_EQ(apps::mojom::OptionalBool::kFalse, HasBadge(profile(), app_id2));
ASSERT_EQ(apps::mojom::OptionalBool::kFalse, HasBadge(profile(), app_id3));
const std::string notification_id = "notification-id";
auto notification = CreateNotification(notification_id, origin);
NotificationDisplayService::GetForProfile(profile())->Display(
NotificationHandler::Type::WEB_NON_PERSISTENT, *notification,
/*metadata=*/nullptr);
ASSERT_EQ(apps::mojom::OptionalBool::kTrue, HasBadge(profile(), app_id1));
ASSERT_EQ(apps::mojom::OptionalBool::kTrue, HasBadge(profile(), app_id2));
ASSERT_EQ(apps::mojom::OptionalBool::kFalse, HasBadge(profile(), app_id3));
histogram_tester.ExpectUniqueSample(
"ChromeOS.Apps.NumberOfAppsForNotification", true, 1);
RemoveNotification(profile(), notification_id);
ASSERT_EQ(apps::mojom::OptionalBool::kFalse, HasBadge(profile(), app_id1));
ASSERT_EQ(apps::mojom::OptionalBool::kFalse, HasBadge(profile(), app_id2));
ASSERT_EQ(apps::mojom::OptionalBool::kFalse, HasBadge(profile(), app_id3));
}
IN_PROC_BROWSER_TEST_F(AppNotificationsWebNotificationTest,
NonPersistentNotificationWhenInstallAndUninstallApp) {
base::HistogramTester histogram_tester;
// Send the notification 1 before installing apps.
const GURL origin = GetOrigin();
const std::string notification_id1 = "notification-id1";
auto notification = CreateNotification(notification_id1, origin);
NotificationDisplayService::GetForProfile(profile())->Display(
NotificationHandler::Type::WEB_NON_PERSISTENT, *notification,
/*metadata=*/nullptr);
// Install apps.
std::string app_id1 = CreateWebApp(GetUrl1(), GetScope1());
std::string app_id2 = CreateWebApp(GetUrl2(), GetScope2());
std::string app_id3 = CreateWebApp(GetUrl3(), GetScope3());
ASSERT_EQ(apps::mojom::OptionalBool::kFalse, HasBadge(profile(), app_id1));
ASSERT_EQ(apps::mojom::OptionalBool::kFalse, HasBadge(profile(), app_id2));
ASSERT_EQ(apps::mojom::OptionalBool::kFalse, HasBadge(profile(), app_id3));
histogram_tester.ExpectTotalCount("ChromeOS.Apps.NumberOfAppsForNotification",
0);
// Send the notification 2.
const std::string notification_id2 = "notification-id2";
notification = CreateNotification(notification_id2, origin);
NotificationDisplayService::GetForProfile(profile())->Display(
NotificationHandler::Type::WEB_NON_PERSISTENT, *notification,
/*metadata=*/nullptr);
ASSERT_EQ(apps::mojom::OptionalBool::kTrue, HasBadge(profile(), app_id1));
ASSERT_EQ(apps::mojom::OptionalBool::kTrue, HasBadge(profile(), app_id2));
ASSERT_EQ(apps::mojom::OptionalBool::kFalse, HasBadge(profile(), app_id3));
histogram_tester.ExpectUniqueSample(
"ChromeOS.Apps.NumberOfAppsForNotification", true, 1);
// Uninstall the app 1. The notification badge for app 2 and app 3 should not
// be affected.
UninstallWebApp(app_id1);
ASSERT_EQ(apps::mojom::OptionalBool::kTrue, HasBadge(profile(), app_id2));
ASSERT_EQ(apps::mojom::OptionalBool::kFalse, HasBadge(profile(), app_id3));
// Re-install the app 1.
app_id1 = CreateWebApp(GetUrl1(), GetScope1());
ASSERT_EQ(apps::mojom::OptionalBool::kFalse, HasBadge(profile(), app_id1));
ASSERT_EQ(apps::mojom::OptionalBool::kTrue, HasBadge(profile(), app_id2));
ASSERT_EQ(apps::mojom::OptionalBool::kFalse, HasBadge(profile(), app_id3));
// Send the notification 3.
const std::string notification_id3 = "notification-id3";
notification = CreateNotification(notification_id3, origin);
NotificationDisplayService::GetForProfile(profile())->Display(
NotificationHandler::Type::WEB_NON_PERSISTENT, *notification,
/*metadata=*/nullptr);
ASSERT_EQ(apps::mojom::OptionalBool::kTrue, HasBadge(profile(), app_id1));
ASSERT_EQ(apps::mojom::OptionalBool::kTrue, HasBadge(profile(), app_id2));
ASSERT_EQ(apps::mojom::OptionalBool::kFalse, HasBadge(profile(), app_id3));
histogram_tester.ExpectUniqueSample(
"ChromeOS.Apps.NumberOfAppsForNotification", true, 2);
// Remove the notification 3
RemoveNotification(profile(), notification_id3);
ASSERT_EQ(apps::mojom::OptionalBool::kFalse, HasBadge(profile(), app_id1));
ASSERT_EQ(apps::mojom::OptionalBool::kTrue, HasBadge(profile(), app_id2));
ASSERT_EQ(apps::mojom::OptionalBool::kFalse, HasBadge(profile(), app_id3));
// Remove the notification 1
RemoveNotification(profile(), notification_id1);
ASSERT_EQ(apps::mojom::OptionalBool::kFalse, HasBadge(profile(), app_id1));
ASSERT_EQ(apps::mojom::OptionalBool::kTrue, HasBadge(profile(), app_id2));
ASSERT_EQ(apps::mojom::OptionalBool::kFalse, HasBadge(profile(), app_id3));
// Remove the notification 2
RemoveNotification(profile(), notification_id2);
ASSERT_EQ(apps::mojom::OptionalBool::kFalse, HasBadge(profile(), app_id1));
ASSERT_EQ(apps::mojom::OptionalBool::kFalse, HasBadge(profile(), app_id2));
ASSERT_EQ(apps::mojom::OptionalBool::kFalse, HasBadge(profile(), app_id3));
}
class FakeArcNotificationManagerDelegate
: public ash::ArcNotificationManagerDelegate {
public:
FakeArcNotificationManagerDelegate() = default;
~FakeArcNotificationManagerDelegate() override = default;
// ArcNotificationManagerDelegate:
bool IsPublicSessionOrKiosk() const override { return false; }
void ShowMessageCenter() override {}
void HideMessageCenter() override {}
};
class AppNotificationsArcNotificationTest
: public extensions::PlatformAppBrowserTest {
protected:
// extensions::PlatformAppBrowserTest:
void SetUpCommandLine(base::CommandLine* command_line) override {
extensions::PlatformAppBrowserTest::SetUpCommandLine(command_line);
arc::SetArcAvailableCommandLineForTesting(command_line);
}
void SetUpInProcessBrowserTestFixture() override {
extensions::PlatformAppBrowserTest::SetUpInProcessBrowserTestFixture();
arc::ArcSessionManager::SetUiEnabledForTesting(false);
}
void SetUpOnMainThread() override {
extensions::PlatformAppBrowserTest::SetUpOnMainThread();
arc::SetArcPlayStoreEnabledForProfile(profile(), true);
// This ensures app_prefs()->GetApp() below never returns nullptr.
base::RunLoop run_loop;
app_prefs()->SetDefaultAppsReadyCallback(run_loop.QuitClosure());
run_loop.Run();
StartInstance();
arc_notification_manager_ = std::make_unique<ash::ArcNotificationManager>();
arc_notification_manager_->Init(
std::make_unique<FakeArcNotificationManagerDelegate>(),
EmptyAccountId(), message_center::MessageCenter::Get());
ash::ArcNotificationsHostInitializer::Observer* observer =
apps::ArcAppsFactory::GetInstance()->GetForProfile(profile());
observer->OnSetArcNotificationsInstance(arc_notification_manager_.get());
}
void TearDownOnMainThread() override {
arc_notification_manager_.reset();
StopInstance();
base::RunLoop().RunUntilIdle();
extensions::PlatformAppBrowserTest::TearDownOnMainThread();
}
void InstallTestApps() {
app_host()->OnAppListRefreshed(GetTestAppsList());
SendPackageAdded(kTestAppPackage1, false);
SendPackageAdded(kTestAppPackage2, false);
}
void SendPackageAdded(const std::string& package_name, bool package_synced) {
auto package_info = arc::mojom::ArcPackageInfo::New();
package_info->package_name = package_name;
package_info->package_version = 1;
package_info->last_backup_android_id = 1;
package_info->last_backup_time = 1;
package_info->sync = package_synced;
package_info->system = false;
app_instance_->SendPackageAdded(std::move(package_info));
base::RunLoop().RunUntilIdle();
}
void SendPackageRemoved(const std::string& package_name) {
app_host()->OnPackageRemoved(package_name);
// Ensure async callbacks from the resulting observer calls are run.
base::RunLoop().RunUntilIdle();
}
void StartInstance() {
app_instance_ = std::make_unique<arc::FakeAppInstance>(app_host());
arc_bridge_service()->app()->SetInstance(app_instance_.get());
}
void StopInstance() {
if (app_instance_)
arc_bridge_service()->app()->CloseInstance(app_instance_.get());
arc_session_manager()->Shutdown();
}
void CreateNotificationWithKey(const std::string& key,
const std::string& package_name) {
auto data = arc::mojom::ArcNotificationData::New();
data->key = key;
data->title = "TITLE";
data->message = "MESSAGE";
data->package_name = package_name;
arc_notification_manager_->OnNotificationPosted(std::move(data));
}
void RemoveNotificationWithKey(const std::string& key) {
arc_notification_manager_->OnNotificationRemoved(key);
}
ArcAppListPrefs* app_prefs() { return ArcAppListPrefs::Get(profile()); }
// Returns as AppHost interface in order to access to private implementation
// of the interface.
arc::mojom::AppHost* app_host() { return app_prefs(); }
private:
arc::ArcSessionManager* arc_session_manager() {
return arc::ArcSessionManager::Get();
}
arc::ArcBridgeService* arc_bridge_service() {
return arc::ArcServiceManager::Get()->arc_bridge_service();
}
std::unique_ptr<ash::ArcNotificationManager> arc_notification_manager_;
std::unique_ptr<arc::FakeAppInstance> app_instance_;
};
IN_PROC_BROWSER_TEST_F(AppNotificationsArcNotificationTest,
AddAndRemoveNotification) {
// Install app to remember existing apps.
InstallTestApps();
const std::string app_id1 = GetTestAppId(kTestAppPackage1, kTestAppActivity1);
const std::string app_id2 = GetTestAppId(kTestAppPackage2, kTestAppActivity2);
ASSERT_EQ(apps::mojom::OptionalBool::kFalse, HasBadge(profile(), app_id1));
ASSERT_EQ(apps::mojom::OptionalBool::kFalse, HasBadge(profile(), app_id2));
const std::string notification_key1 = "notification_key1";
CreateNotificationWithKey(notification_key1, kTestAppPackage1);
ASSERT_EQ(apps::mojom::OptionalBool::kTrue, HasBadge(profile(), app_id1));
ASSERT_EQ(apps::mojom::OptionalBool::kFalse, HasBadge(profile(), app_id2));
const std::string notification_key2 = "notification_key2";
CreateNotificationWithKey(notification_key2, kTestAppPackage2);
ASSERT_EQ(apps::mojom::OptionalBool::kTrue, HasBadge(profile(), app_id1));
ASSERT_EQ(apps::mojom::OptionalBool::kTrue, HasBadge(profile(), app_id2));
RemoveNotificationWithKey(notification_key1);
ASSERT_EQ(apps::mojom::OptionalBool::kFalse, HasBadge(profile(), app_id1));
ASSERT_EQ(apps::mojom::OptionalBool::kTrue, HasBadge(profile(), app_id2));
RemoveNotificationWithKey(notification_key2);
ASSERT_EQ(apps::mojom::OptionalBool::kFalse, HasBadge(profile(), app_id1));
ASSERT_EQ(apps::mojom::OptionalBool::kFalse, HasBadge(profile(), app_id2));
}
IN_PROC_BROWSER_TEST_F(AppNotificationsArcNotificationTest,
MultipleNotificationsWhenUninstallApp) {
// Install apps to remember existing apps.
InstallTestApps();
const std::string app_id1 = GetTestAppId(kTestAppPackage1, kTestAppActivity1);
const std::string app_id2 = GetTestAppId(kTestAppPackage2, kTestAppActivity2);
ASSERT_EQ(apps::mojom::OptionalBool::kFalse, HasBadge(profile(), app_id1));
ASSERT_EQ(apps::mojom::OptionalBool::kFalse, HasBadge(profile(), app_id2));
// Sent 2 notifications for the app 1.
const std::string notification_key1 = "notification_key1";
CreateNotificationWithKey(notification_key1, kTestAppPackage1);
ASSERT_EQ(apps::mojom::OptionalBool::kTrue, HasBadge(profile(), app_id1));
ASSERT_EQ(apps::mojom::OptionalBool::kFalse, HasBadge(profile(), app_id2));
const std::string notification_key2 = "notification_key2";
CreateNotificationWithKey(notification_key2, kTestAppPackage1);
ASSERT_EQ(apps::mojom::OptionalBool::kTrue, HasBadge(profile(), app_id1));
ASSERT_EQ(apps::mojom::OptionalBool::kFalse, HasBadge(profile(), app_id2));
// Remove the app 1.
SendPackageRemoved(app_id1);
// Sent 1 notification for the app 2.
const std::string notification_key3 = "notification_key3";
CreateNotificationWithKey(notification_key3, kTestAppPackage2);
ASSERT_EQ(apps::mojom::OptionalBool::kTrue, HasBadge(profile(), app_id2));
// Remove the notification for the app 2.
RemoveNotificationWithKey(notification_key3);
ASSERT_EQ(apps::mojom::OptionalBool::kFalse, HasBadge(profile(), app_id2));
// Sent 2 notifications for the app 2.
const std::string notification_key4 = "notification_key4";
CreateNotificationWithKey(notification_key4, kTestAppPackage2);
ASSERT_EQ(apps::mojom::OptionalBool::kTrue, HasBadge(profile(), app_id2));
const std::string notification_key5 = "notification_key5";
CreateNotificationWithKey(notification_key5, kTestAppPackage1);
ASSERT_EQ(apps::mojom::OptionalBool::kTrue, HasBadge(profile(), app_id2));
// Remove notifications for the app2.
RemoveNotificationWithKey(notification_key5);
ASSERT_EQ(apps::mojom::OptionalBool::kTrue, HasBadge(profile(), app_id2));
RemoveNotificationWithKey(notification_key4);
ASSERT_EQ(apps::mojom::OptionalBool::kFalse, HasBadge(profile(), app_id2));
// Remove the app 2.
SendPackageRemoved(app_id2);
}
IN_PROC_BROWSER_TEST_F(AppNotificationsArcNotificationTest,
MultipleNotificationsWhenInstallAndUninstallApp) {
// Install apps to remember existing apps.
InstallTestApps();
const std::string app_id1 = GetTestAppId(kTestAppPackage1, kTestAppActivity1);
const std::string app_id2 = GetTestAppId(kTestAppPackage2, kTestAppActivity2);
ASSERT_EQ(apps::mojom::OptionalBool::kFalse, HasBadge(profile(), app_id1));
ASSERT_EQ(apps::mojom::OptionalBool::kFalse, HasBadge(profile(), app_id2));
// Sent 2 notifications for the app 1, and 1 notification for the app 2.
const std::string notification_key1 = "notification_key1";
CreateNotificationWithKey(notification_key1, kTestAppPackage1);
ASSERT_EQ(apps::mojom::OptionalBool::kTrue, HasBadge(profile(), app_id1));
ASSERT_EQ(apps::mojom::OptionalBool::kFalse, HasBadge(profile(), app_id2));
const std::string notification_key2 = "notification_key2";
CreateNotificationWithKey(notification_key2, kTestAppPackage1);
ASSERT_EQ(apps::mojom::OptionalBool::kTrue, HasBadge(profile(), app_id1));
ASSERT_EQ(apps::mojom::OptionalBool::kFalse, HasBadge(profile(), app_id2));
// Sent 1 notification for the app 2.
const std::string notification_key3 = "notification_key3";
CreateNotificationWithKey(notification_key3, kTestAppPackage2);
ASSERT_EQ(apps::mojom::OptionalBool::kTrue, HasBadge(profile(), app_id1));
ASSERT_EQ(apps::mojom::OptionalBool::kTrue, HasBadge(profile(), app_id2));
RemoveNotificationWithKey(notification_key1);
ASSERT_EQ(apps::mojom::OptionalBool::kTrue, HasBadge(profile(), app_id1));
ASSERT_EQ(apps::mojom::OptionalBool::kTrue, HasBadge(profile(), app_id2));
// Uninstall the app 2.
UninstallApp(profile(), app_id2);
ASSERT_EQ(apps::mojom::OptionalBool::kTrue, HasBadge(profile(), app_id1));
// Uninstall the app 1.
UninstallApp(profile(), app_id1);
// Reinstall apps
InstallTestApps();
ASSERT_EQ(apps::mojom::OptionalBool::kFalse, HasBadge(profile(), app_id1));
ASSERT_EQ(apps::mojom::OptionalBool::kFalse, HasBadge(profile(), app_id2));
// Sent 2 notifications for the app 2, and 1 notification for the app 1.
const std::string notification_key4 = "notification_key4";
CreateNotificationWithKey(notification_key4, kTestAppPackage2);
ASSERT_EQ(apps::mojom::OptionalBool::kFalse, HasBadge(profile(), app_id1));
ASSERT_EQ(apps::mojom::OptionalBool::kTrue, HasBadge(profile(), app_id2));
const std::string notification_key5 = "notification_key5";
CreateNotificationWithKey(notification_key5, kTestAppPackage1);
ASSERT_EQ(apps::mojom::OptionalBool::kTrue, HasBadge(profile(), app_id1));
ASSERT_EQ(apps::mojom::OptionalBool::kTrue, HasBadge(profile(), app_id2));
const std::string notification_key6 = "notification_key6";
CreateNotificationWithKey(notification_key6, kTestAppPackage2);
ASSERT_EQ(apps::mojom::OptionalBool::kTrue, HasBadge(profile(), app_id1));
ASSERT_EQ(apps::mojom::OptionalBool::kTrue, HasBadge(profile(), app_id2));
// Remove notifications
RemoveNotificationWithKey(notification_key5);
ASSERT_EQ(apps::mojom::OptionalBool::kFalse, HasBadge(profile(), app_id1));
ASSERT_EQ(apps::mojom::OptionalBool::kTrue, HasBadge(profile(), app_id2));
RemoveNotificationWithKey(notification_key4);
ASSERT_EQ(apps::mojom::OptionalBool::kFalse, HasBadge(profile(), app_id1));
ASSERT_EQ(apps::mojom::OptionalBool::kTrue, HasBadge(profile(), app_id2));
RemoveNotificationWithKey(notification_key6);
ASSERT_EQ(apps::mojom::OptionalBool::kFalse, HasBadge(profile(), app_id1));
ASSERT_EQ(apps::mojom::OptionalBool::kFalse, HasBadge(profile(), app_id2));
}