diff --git a/components/brave_ads/browser/BUILD.gn b/components/brave_ads/browser/BUILD.gn index 991cd937c82b..43b8c6bb7a25 100644 --- a/components/brave_ads/browser/BUILD.gn +++ b/components/brave_ads/browser/BUILD.gn @@ -68,7 +68,7 @@ source_set("browser") { "notification_helper_android.h", "notification_helper_linux.cc", "notification_helper_linux.h", - "notification_helper_mac.cc", + "notification_helper_mac.mm", "notification_helper_mac.h", "notification_helper_win.cc", "notification_helper_win.h", @@ -88,6 +88,12 @@ source_set("browser") { "//ui/message_center/public/cpp", ] + if (is_mac) { + libs = [ + "UserNotifications.framework", + ] + } + if (is_win) { deps += [ "//ui/views", diff --git a/components/brave_ads/browser/ads_service_impl.cc b/components/brave_ads/browser/ads_service_impl.cc index 9644eac8ed36..6ea111870f5a 100644 --- a/components/brave_ads/browser/ads_service_impl.cc +++ b/components/brave_ads/browser/ads_service_impl.cc @@ -37,6 +37,7 @@ #include "brave/components/brave_rewards/common/pref_names.h" #include "brave/components/services/bat_ads/public/cpp/ads_client_mojo_bridge.h" #include "brave/components/services/bat_ads/public/interfaces/bat_ads.mojom.h" +#include "brave/components/brave_ads/browser/notification_helper.h" #include "chrome/browser/browser_process.h" #include "chrome/browser/notifications/notification_display_service.h" #include "chrome/browser/notifications/notification_display_service_impl.h" @@ -1922,7 +1923,7 @@ void AdsServiceImpl::ShowNotification( #endif } -bool AdsServiceImpl::ShouldShowNotifications() const { +bool AdsServiceImpl::ShouldShowNotifications() { return NotificationHelper::GetInstance()->ShouldShowNotifications(); } diff --git a/components/brave_ads/browser/ads_service_impl.h b/components/brave_ads/browser/ads_service_impl.h index ae3aaade20ff..8bb1c386dbac 100644 --- a/components/brave_ads/browser/ads_service_impl.h +++ b/components/brave_ads/browser/ads_service_impl.h @@ -359,7 +359,7 @@ class AdsServiceImpl : public AdsService, void ShowNotification( std::unique_ptr info) override; - bool ShouldShowNotifications() const override; + bool ShouldShowNotifications() override; void CloseNotification( const std::string& id) override; diff --git a/components/brave_ads/browser/notification_helper.cc b/components/brave_ads/browser/notification_helper.cc index 2988f2157e0d..160ce961e2a3 100644 --- a/components/brave_ads/browser/notification_helper.cc +++ b/components/brave_ads/browser/notification_helper.cc @@ -7,15 +7,22 @@ namespace brave_ads { +NotificationHelper* g_notification_helper_for_testing = nullptr; + NotificationHelper::NotificationHelper() = default; NotificationHelper::~NotificationHelper() = default; -bool NotificationHelper::ShouldShowNotifications() const { +void NotificationHelper::set_for_testing( + NotificationHelper* notification_helper) { + g_notification_helper_for_testing = notification_helper; +} + +bool NotificationHelper::ShouldShowNotifications() { return true; } -bool NotificationHelper::ShowMyFirstAdNotification() const { +bool NotificationHelper::ShowMyFirstAdNotification() { return false; } @@ -23,9 +30,17 @@ bool NotificationHelper::CanShowBackgroundNotifications() const { return true; } -#if !defined(OS_MACOSX) && !defined(OS_WIN) && !defined(OS_LINUX) && !defined(OS_ANDROID) // NOLINT NotificationHelper* NotificationHelper::GetInstance() { - // just return a dummy notification helper for all other platforms + if (g_notification_helper_for_testing) { + return g_notification_helper_for_testing; + } + + return GetInstanceImpl(); +} + +#if !defined(OS_MACOSX) && !defined(OS_WIN) && !defined(OS_LINUX) && !defined(OS_ANDROID) // NOLINT +NotificationHelper* NotificationHelper::GetInstanceImpl() { + // Return a default notification helper for unsupported platforms return base::Singleton::get(); } #endif diff --git a/components/brave_ads/browser/notification_helper.h b/components/brave_ads/browser/notification_helper.h index 792d847c0378..e3875938365f 100644 --- a/components/brave_ads/browser/notification_helper.h +++ b/components/brave_ads/browser/notification_helper.h @@ -6,8 +6,6 @@ #ifndef BRAVE_COMPONENTS_BRAVE_ADS_BROWSER_NOTIFICATION_HELPER_H_ #define BRAVE_COMPONENTS_BRAVE_ADS_BROWSER_NOTIFICATION_HELPER_H_ -#include - #include "base/macros.h" #include "base/memory/singleton.h" #include "build/build_config.h" @@ -18,9 +16,12 @@ class NotificationHelper { public: static NotificationHelper* GetInstance(); - virtual bool ShouldShowNotifications() const; + void set_for_testing( + NotificationHelper* notification_helper); + + virtual bool ShouldShowNotifications(); - virtual bool ShowMyFirstAdNotification() const; + virtual bool ShowMyFirstAdNotification(); virtual bool CanShowBackgroundNotifications() const; @@ -28,6 +29,8 @@ class NotificationHelper { NotificationHelper(); virtual ~NotificationHelper(); + static NotificationHelper* GetInstanceImpl(); + private: friend struct base::DefaultSingletonTraits; DISALLOW_COPY_AND_ASSIGN(NotificationHelper); diff --git a/components/brave_ads/browser/notification_helper_android.cc b/components/brave_ads/browser/notification_helper_android.cc index dc3caa6ef056..1832d8e9c486 100644 --- a/components/brave_ads/browser/notification_helper_android.cc +++ b/components/brave_ads/browser/notification_helper_android.cc @@ -28,7 +28,7 @@ NotificationHelperAndroid::NotificationHelperAndroid() = default; NotificationHelperAndroid::~NotificationHelperAndroid() = default; -bool NotificationHelperAndroid::ShouldShowNotifications() const { +bool NotificationHelperAndroid::ShouldShowNotifications() { JNIEnv* env = base::android::AttachCurrentThread(); int status = Java_NotificationSystemStatusUtil_getAppNotificationStatus(env); bool is_notifications_enabled = (status == kAppNotificationsStatusEnabled || @@ -40,12 +40,11 @@ bool NotificationHelperAndroid::ShouldShowNotifications() const { auto should_show_notifications = CanShowBackgroundNotifications() || is_foreground; - return is_notifications_enabled && - is_notification_channel_enabled && - should_show_notifications; + return is_notifications_enabled && is_notification_channel_enabled && + should_show_notifications; } -bool NotificationHelperAndroid::ShowMyFirstAdNotification() const { +bool NotificationHelperAndroid::ShowMyFirstAdNotification() { if (!ShouldShowNotifications()) { return false; } @@ -61,6 +60,16 @@ bool NotificationHelperAndroid::CanShowBackgroundNotifications() const { return Java_BraveAdsSignupDialog_showAdsInBackground(env); } +NotificationHelperAndroid* NotificationHelperAndroid::GetInstanceImpl() { + return base::Singleton::get(); +} + +NotificationHelper* NotificationHelper::GetInstanceImpl() { + return NotificationHelperAndroid::GetInstanceImpl(); +} + +/////////////////////////////////////////////////////////////////////////////// + bool NotificationHelperAndroid::IsBraveAdsNotificationChannelEnabled() const { if (GetOperatingSystemVersion() < kMinimumVersionForNotificationChannels) { return true; @@ -86,12 +95,4 @@ int NotificationHelperAndroid::GetOperatingSystemVersion() const { return major_version; } -NotificationHelperAndroid* NotificationHelperAndroid::GetInstance() { - return base::Singleton::get(); -} - -NotificationHelper* NotificationHelper::GetInstance() { - return NotificationHelperAndroid::GetInstance(); -} - } // namespace brave_ads diff --git a/components/brave_ads/browser/notification_helper_android.h b/components/brave_ads/browser/notification_helper_android.h index 91821187a8b7..11964fe7a060 100644 --- a/components/brave_ads/browser/notification_helper_android.h +++ b/components/brave_ads/browser/notification_helper_android.h @@ -8,8 +8,6 @@ #include -#include "base/macros.h" -#include "base/memory/singleton.h" #include "base/memory/weak_ptr.h" #include "brave/components/brave_ads/browser/notification_helper.h" @@ -17,16 +15,16 @@ namespace brave_ads { -class NotificationHelperAndroid : - public NotificationHelper, - public base::SupportsWeakPtr { +class NotificationHelperAndroid + : public NotificationHelper, + public base::SupportsWeakPtr { public: + static NotificationHelperAndroid* GetInstanceImpl(); + + private: NotificationHelperAndroid(); ~NotificationHelperAndroid() override; - static NotificationHelperAndroid* GetInstance(); - - private: bool IsBraveAdsNotificationChannelEnabled() const; std::unique_ptr channels_provider_ = @@ -35,9 +33,9 @@ class NotificationHelperAndroid : int GetOperatingSystemVersion() const; // NotificationHelper impl - bool ShouldShowNotifications() const override; + bool ShouldShowNotifications() override; - bool ShowMyFirstAdNotification() const override; + bool ShowMyFirstAdNotification() override; bool CanShowBackgroundNotifications() const override; diff --git a/components/brave_ads/browser/notification_helper_linux.cc b/components/brave_ads/browser/notification_helper_linux.cc index 9bdc53e17f28..4813762a73ce 100644 --- a/components/brave_ads/browser/notification_helper_linux.cc +++ b/components/brave_ads/browser/notification_helper_linux.cc @@ -4,7 +4,6 @@ * You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "brave/components/brave_ads/browser/notification_helper_linux.h" - #include "chrome/browser/fullscreen.h" namespace brave_ads { @@ -13,11 +12,20 @@ NotificationHelperLinux::NotificationHelperLinux() = default; NotificationHelperLinux::~NotificationHelperLinux() = default; -bool NotificationHelperLinux::ShouldShowNotifications() const { - return !IsFullScreenMode(); +bool NotificationHelperLinux::ShouldShowNotifications() { + if (IsFullScreenMode()) { + LOG(WARNING) << "Notification not made: Full screen mode"; + return false; + } + + // TODO(https://github.com/brave/brave-browser/issues/5542): Investigate how + // we can detect if notifications are enabled within the Linux operating + // system + + return true; } -bool NotificationHelperLinux::ShowMyFirstAdNotification() const { +bool NotificationHelperLinux::ShowMyFirstAdNotification() { return false; } @@ -25,12 +33,12 @@ bool NotificationHelperLinux::CanShowBackgroundNotifications() const { return true; } -NotificationHelperLinux* NotificationHelperLinux::GetInstance() { +NotificationHelperLinux* NotificationHelperLinux::GetInstanceImpl() { return base::Singleton::get(); } -NotificationHelper* NotificationHelper::GetInstance() { - return NotificationHelperLinux::GetInstance(); +NotificationHelper* NotificationHelper::GetInstanceImpl() { + return NotificationHelperLinux::GetInstanceImpl(); } } // namespace brave_ads diff --git a/components/brave_ads/browser/notification_helper_linux.h b/components/brave_ads/browser/notification_helper_linux.h index f5561658c02c..4360c4bc435a 100644 --- a/components/brave_ads/browser/notification_helper_linux.h +++ b/components/brave_ads/browser/notification_helper_linux.h @@ -6,28 +6,26 @@ #ifndef BRAVE_COMPONENTS_BRAVE_ADS_BROWSER_NOTIFICATION_HELPER_LINUX_H_ #define BRAVE_COMPONENTS_BRAVE_ADS_BROWSER_NOTIFICATION_HELPER_LINUX_H_ -#include "base/macros.h" -#include "base/memory/singleton.h" #include "base/memory/weak_ptr.h" #include "brave/components/brave_ads/browser/notification_helper.h" namespace brave_ads { -class NotificationHelperLinux : - public NotificationHelper, - public base::SupportsWeakPtr { +class NotificationHelperLinux + : public NotificationHelper, + public base::SupportsWeakPtr { public: + static NotificationHelperLinux* GetInstanceImpl(); + + private: NotificationHelperLinux(); ~NotificationHelperLinux() override; - static NotificationHelperLinux* GetInstance(); - - private: // NotificationHelper impl - bool ShouldShowNotifications() const override; + bool ShouldShowNotifications() override; - bool ShowMyFirstAdNotification() const override; + bool ShowMyFirstAdNotification() override; bool CanShowBackgroundNotifications() const override; diff --git a/components/brave_ads/browser/notification_helper_mac.cc b/components/brave_ads/browser/notification_helper_mac.cc deleted file mode 100644 index 7d873b289e87..000000000000 --- a/components/brave_ads/browser/notification_helper_mac.cc +++ /dev/null @@ -1,36 +0,0 @@ -/* Copyright (c) 2019 The Brave Authors. All rights reserved. - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this file, - * You can obtain one at http://mozilla.org/MPL/2.0/. */ - -#include "brave/components/brave_ads/browser/notification_helper_mac.h" - -#include "chrome/browser/fullscreen.h" - -namespace brave_ads { - -NotificationHelperMac::NotificationHelperMac() = default; - -NotificationHelperMac::~NotificationHelperMac() = default; - -bool NotificationHelperMac::ShouldShowNotifications() const { - return !IsFullScreenMode(); -} - -bool NotificationHelperMac::ShowMyFirstAdNotification() const { - return false; -} - -bool NotificationHelperMac::CanShowBackgroundNotifications() const { - return true; -} - -NotificationHelperMac* NotificationHelperMac::GetInstance() { - return base::Singleton::get(); -} - -NotificationHelper* NotificationHelper::GetInstance() { - return NotificationHelperMac::GetInstance(); -} - -} // namespace brave_ads diff --git a/components/brave_ads/browser/notification_helper_mac.h b/components/brave_ads/browser/notification_helper_mac.h index f2ba4727e5a4..211bcd2f49b5 100644 --- a/components/brave_ads/browser/notification_helper_mac.h +++ b/components/brave_ads/browser/notification_helper_mac.h @@ -6,28 +6,28 @@ #ifndef BRAVE_COMPONENTS_BRAVE_ADS_BROWSER_NOTIFICATION_HELPER_MAC_H_ #define BRAVE_COMPONENTS_BRAVE_ADS_BROWSER_NOTIFICATION_HELPER_MAC_H_ -#include "base/macros.h" -#include "base/memory/singleton.h" #include "base/memory/weak_ptr.h" #include "brave/components/brave_ads/browser/notification_helper.h" namespace brave_ads { -class NotificationHelperMac : - public NotificationHelper, - public base::SupportsWeakPtr { +class NotificationHelperMac + : public NotificationHelper, + public base::SupportsWeakPtr { public: + static NotificationHelperMac* GetInstanceImpl(); + + private: NotificationHelperMac(); ~NotificationHelperMac() override; - static NotificationHelperMac* GetInstance(); + bool IsNotificationsEnabled() const; - private: // NotificationHelper impl - bool ShouldShowNotifications() const override; + bool ShouldShowNotifications() override; - bool ShowMyFirstAdNotification() const override; + bool ShowMyFirstAdNotification() override; bool CanShowBackgroundNotifications() const override; diff --git a/components/brave_ads/browser/notification_helper_mac.mm b/components/brave_ads/browser/notification_helper_mac.mm new file mode 100644 index 000000000000..ae86dcfdf918 --- /dev/null +++ b/components/brave_ads/browser/notification_helper_mac.mm @@ -0,0 +1,126 @@ +/* Copyright (c) 2019 The Brave Authors. All rights reserved. + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#import +#import + +#include "base/mac/mac_util.h" +#include "base/feature_list.h" +#include "chrome/common/chrome_features.h" +#include "base/logging.h" + +#include "brave/components/brave_ads/browser/notification_helper_mac.h" +#include "chrome/browser/fullscreen.h" + +namespace brave_ads { + +NotificationHelperMac::NotificationHelperMac() = default; + +NotificationHelperMac::~NotificationHelperMac() = default; + +bool NotificationHelperMac::ShouldShowNotifications() { + if (IsFullScreenMode()) { + LOG(WARNING) << "Notification not made: Full screen mode"; + return false; + } + + if (base::mac::IsAtMostOS10_13()) { + LOG(WARNING) << "Native notifications are not supported on macOS prior" + " to macOS 10.14 so falling back to Message Center"; + return true; + } + + if (!base::FeatureList::IsEnabled(features::kNativeNotifications)) { + LOG(WARNING) << "Native notification feature is disabled so falling back to" + " Message Center"; + return true; + } + + if (!IsNotificationsEnabled()) { + LOG(INFO) << "Notification not made: Notifications are disabled"; + return false; + } + + return true; +} + +bool NotificationHelperMac::ShowMyFirstAdNotification() { + return false; +} + +bool NotificationHelperMac::CanShowBackgroundNotifications() const { + return true; +} + +NotificationHelperMac* NotificationHelperMac::GetInstanceImpl() { + return base::Singleton::get(); +} + +NotificationHelper* NotificationHelper::GetInstanceImpl() { + return NotificationHelperMac::GetInstanceImpl(); +} + +/////////////////////////////////////////////////////////////////////////////// + +bool NotificationHelperMac::IsNotificationsEnabled() const { +#if !defined(OFFICIAL_BUILD) + LOG(WARNING) << "Unable to detect the status of native notifications on non" + " official builds as the app is not code signed"; + return true; +#else + // TODO(https://openradar.appspot.com/27768556): We must mock this function + // using NotificationHelperMock as a workaround to UNUserNotificationCenter + // throwing an exception during tests + + if (@available(macOS 10.14, *)) { + __block bool is_authorized = false; + + dispatch_semaphore_t semaphore = dispatch_semaphore_create(0); + + UNUserNotificationCenter *notificationCenter = + [UNUserNotificationCenter currentNotificationCenter]; + + [notificationCenter getNotificationSettingsWithCompletionHandler: + ^(UNNotificationSettings * _Nonnull settings) { + switch (settings.authorizationStatus) { + case UNAuthorizationStatusDenied: { + LOG(WARNING) << "Notification authorization status denied"; + is_authorized = false; + break; + } + + case UNAuthorizationStatusNotDetermined: { + LOG(INFO) << "Notification authorization status not determined"; + is_authorized = true; + break; + } + + case UNAuthorizationStatusAuthorized: { + LOG(INFO) << "Notification authorization status authorized"; + is_authorized = true; + break; + } + + case UNAuthorizationStatusProvisional: { + LOG(INFO) << "Notification authorization status provisional"; + is_authorized = true; + break; + } + } + + dispatch_semaphore_signal(semaphore); + }]; + + dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER); + dispatch_release(semaphore); + + return is_authorized; + } + + return true; +#endif +} + +} // namespace brave_ads diff --git a/components/brave_ads/browser/notification_helper_mock.cc b/components/brave_ads/browser/notification_helper_mock.cc new file mode 100644 index 000000000000..ca7e32f40e8d --- /dev/null +++ b/components/brave_ads/browser/notification_helper_mock.cc @@ -0,0 +1,14 @@ +/* Copyright (c) 2019 The Brave Authors. All rights reserved. + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "brave/components/brave_ads/browser/notification_helper_mock.h" + +namespace brave_ads { + +NotificationHelperMock::NotificationHelperMock() = default; + +NotificationHelperMock::~NotificationHelperMock() = default; + +} // namespace brave_ads diff --git a/components/brave_ads/browser/notification_helper_mock.h b/components/brave_ads/browser/notification_helper_mock.h new file mode 100644 index 000000000000..815cb80b0efe --- /dev/null +++ b/components/brave_ads/browser/notification_helper_mock.h @@ -0,0 +1,30 @@ +/* Copyright (c) 2019 The Brave Authors. All rights reserved. + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef BRAVE_COMPONENTS_BRAVE_ADS_BROWSER_NOTIFICATION_HELPER_MOCK_H_ +#define BRAVE_COMPONENTS_BRAVE_ADS_BROWSER_NOTIFICATION_HELPER_MOCK_H_ + +#include "brave/components/brave_ads/browser/notification_helper.h" + +#include "testing/gmock/include/gmock/gmock.h" + +namespace brave_ads { + +class NotificationHelperMock : public NotificationHelper { + public: + NotificationHelperMock(); + ~NotificationHelperMock() override; + + MOCK_METHOD0(ShouldShowNotifications, bool()); + MOCK_METHOD0(ShowMyFirstAdNotification, bool()); + MOCK_CONST_METHOD0(CanShowBackgroundNotifications, bool()); + + private: + DISALLOW_COPY_AND_ASSIGN(NotificationHelperMock); +}; + +} // namespace brave_ads + +#endif // BRAVE_COMPONENTS_BRAVE_ADS_BROWSER_NOTIFICATION_HELPER_MOCK_H_ diff --git a/components/brave_ads/browser/notification_helper_win.cc b/components/brave_ads/browser/notification_helper_win.cc index dfbf68a8deec..91cb238cfc09 100644 --- a/components/brave_ads/browser/notification_helper_win.cc +++ b/components/brave_ads/browser/notification_helper_win.cc @@ -3,21 +3,104 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this file, * You can obtain one at http://mozilla.org/MPL/2.0/. */ +#include + #include "brave/components/brave_ads/browser/notification_helper_win.h" #include "chrome/browser/fullscreen.h" +#include "base/win/windows_version.h" +#include "base/win/core_winrt_util.h" +#include "base/win/scoped_hstring.h" +#include "base/feature_list.h" +#include "chrome/common/chrome_features.h" +#include "chrome/install_static/install_util.h" +#include "chrome/installer/util/install_util.h" +#include "chrome/installer/util/shell_util.h" +#include "base/logging.h" namespace brave_ads { +// Copied from ntdef.h as not available in the Windows SDK and is required to +// detect if Focus Assist is enabled. Focus Assist is currently undocumented + +typedef __success(return >= 0) LONG NTSTATUS; + +#define NT_SUCCESS(Status) (((NTSTATUS)(Status)) >= 0) + +typedef struct _WNF_STATE_NAME { + ULONG Data[2]; +} WNF_STATE_NAME; + +typedef struct _WNF_STATE_NAME* PWNF_STATE_NAME; +typedef const struct _WNF_STATE_NAME* PCWNF_STATE_NAME; + +typedef struct _WNF_TYPE_ID { + GUID TypeId; +} WNF_TYPE_ID, * PWNF_TYPE_ID; + +typedef const WNF_TYPE_ID* PCWNF_TYPE_ID; + +typedef ULONG WNF_CHANGE_STAMP, * PWNF_CHANGE_STAMP; + +typedef NTSTATUS (NTAPI* PNTQUERYWNFSTATEDATA)( + _In_ PWNF_STATE_NAME StateName, + _In_opt_ PWNF_TYPE_ID TypeId, + _In_opt_ const VOID* ExplicitScope, + _Out_ PWNF_CHANGE_STAMP ChangeStamp, + _Out_writes_bytes_to_opt_(* BufferSize, * BufferSize) PVOID Buffer, + _Inout_ PULONG BufferSize); + +enum FocusAssistResult { + NOT_SUPPORTED = -2, + FAILED = -1, + OFF = 0, + PRIORITY_ONLY = 1, + ALARMS_ONLY = 2 +}; + +/////////////////////////////////////////////////////////////////////////////// + NotificationHelperWin::NotificationHelperWin() = default; NotificationHelperWin::~NotificationHelperWin() = default; -bool NotificationHelperWin::ShouldShowNotifications() const { - return !IsFullScreenMode(); +bool NotificationHelperWin::ShouldShowNotifications() { + if (IsFullScreenMode()) { + LOG(WARNING) << "Notification not made: Full screen mode"; + return false; + } + + if (base::win::GetVersion() < base::win::Version::WIN10_RS4) { + // There was a Microsoft bug in Windows 10 prior to build 17134 (i.e. + // VERSION_WIN10_RS4) causing endless loops in displaying notifications. It + // significantly amplified the memory and CPU usage. Therefore, Windows 10 + // native notifications in Chromium are only enabled for build 17134 and + // later + LOG(WARNING) << "Native notifications are not supported on Windows prior" + " to Windows 10 build 17134 so falling back to Message Center"; + return true; + } + + if (!base::FeatureList::IsEnabled(features::kNativeNotifications)) { + LOG(WARNING) << "Native notification feature is disabled so falling back to" + " Message Center"; + return true; + } + + if (IsFocusAssistEnabled()) { + LOG(INFO) << "Notification not made: Focus assist is enabled"; + return false; + } + + if (!IsNotificationsEnabled()) { + LOG(INFO) << "Notification not made: Notifications are disabled"; + return false; + } + + return true; } -bool NotificationHelperWin::ShowMyFirstAdNotification() const { +bool NotificationHelperWin::ShowMyFirstAdNotification() { return false; } @@ -25,12 +108,171 @@ bool NotificationHelperWin::CanShowBackgroundNotifications() const { return true; } -NotificationHelperWin* NotificationHelperWin::GetInstance() { +NotificationHelperWin* NotificationHelperWin::GetInstanceImpl() { return base::Singleton::get(); } -NotificationHelper* NotificationHelper::GetInstance() { - return NotificationHelperWin::GetInstance(); +NotificationHelper* NotificationHelper::GetInstanceImpl() { + return NotificationHelperWin::GetInstanceImpl(); +} + +/////////////////////////////////////////////////////////////////////////////// + +bool NotificationHelperWin::IsFocusAssistEnabled() const { + const auto nt_query_wnf_state_data_func = + GetProcAddress(GetModuleHandle(L"ntdll"), "NtQueryWnfStateData"); + + const auto nt_query_wnf_state_data = + PNTQUERYWNFSTATEDATA(nt_query_wnf_state_data_func); + + if (!nt_query_wnf_state_data) { + LOG(ERROR) << "Failed to get pointer to NtQueryWnfStateData function"; + return false; + } + + // State name for Focus Assist + WNF_STATE_NAME WNF_SHEL_QUIETHOURS_ACTIVE_PROFILE_CHANGED { + 0xA3BF1C75, 0xD83063E + }; + + WNF_CHANGE_STAMP change_stamp = { // Not used but is required + 0 + }; + + DWORD buffer = 0; + ULONG buffer_size = sizeof(buffer); + + if (!NT_SUCCESS(nt_query_wnf_state_data( + &WNF_SHEL_QUIETHOURS_ACTIVE_PROFILE_CHANGED, nullptr, nullptr, + &change_stamp, &buffer, &buffer_size))) { + LOG(ERROR) << "Failed to get status of Focus Assist"; + return false; + } + + auto result = (FocusAssistResult)buffer; + + switch (result) { + case NOT_SUPPORTED: { + LOG(WARNING) << "Focus Assist is unsupported"; + return false; + } + + case FAILED: { + LOG(WARNING) << "Failed to determine Focus Assist status"; + return false; + } + + case OFF: { + LOG(INFO) << "Focus Assist is disabled"; + return false; + } + + case PRIORITY_ONLY: { + LOG(INFO) << "Focus Assist is set to priority only"; + return true; + } + + case ALARMS_ONLY: { + LOG(INFO) << "Focus Assist is set to alarms only"; + return true; + } + } + + LOG(WARNING) << "Unknown Focus Assist status: " << result; + return false; +} + +bool NotificationHelperWin::IsNotificationsEnabled() { +#if !defined(OFFICIAL_BUILD) + LOG(WARNING) << "Unable to detect the status of native notifications on non" + " official builds as the app is not code signed"; + return true; +#else + HRESULT hr = InitializeToastNotifier(); + auto* notifier = notifier_.Get(); + if (!notifier || FAILED(hr)) { + LOG(ERROR) << "Failed to initialize toast notifier"; + return false; + } + + ABI::Windows::UI::Notifications::NotificationSetting setting; + hr = notifier->get_Setting(&setting); + if (FAILED(hr)) { + LOG(ERROR) << "Failed to get notification settings from toast notifier"; + return false; + } + + switch (setting) { + case ABI::Windows::UI::Notifications:: + NotificationSetting_Enabled: { + LOG(INFO) << "Notifications are enabled"; + return true; + } + + case ABI::Windows::UI::Notifications:: + NotificationSetting_DisabledForUser: { + LOG(WARNING) << "Notifications disabled for user"; + return false; + } + + case ABI::Windows::UI::Notifications:: + NotificationSetting_DisabledForApplication: { + LOG(WARNING) << "Notifications disabled for application"; + return false; + } + + case ABI::Windows::UI::Notifications:: + NotificationSetting_DisabledByGroupPolicy: { + LOG(WARNING) << "Notifications disabled by group policy"; + return false; + } + + case ABI::Windows::UI::Notifications:: + NotificationSetting_DisabledByManifest: { + LOG(WARNING) << "Notifications disabled by manifest"; + return false; + } + } +#endif +} + +base::string16 NotificationHelperWin::GetAppId() const { + return ShellUtil::GetBrowserModelId(InstallUtil::IsPerUserInstall()); +} + +HRESULT NotificationHelperWin::InitializeToastNotifier() { + Microsoft::WRL::ComPtr manager; + + HRESULT hr = CreateActivationFactory( + RuntimeClass_Windows_UI_Notifications_ToastNotificationManager, + manager.GetAddressOf()); + + if (FAILED(hr)) { + LOG(ERROR) << "Failed to create activation factory"; + return hr; + } + + auto application_id = base::win::ScopedHString::Create(GetAppId()); + hr = manager->CreateToastNotifierWithId(application_id.get(), ¬ifier_); + if (FAILED(hr)) { + LOG(ERROR) << "Failed to create toast notifier"; + return hr; + } + + return hr; +} + +// Templated wrapper for ABI::Windows::Foundation::GetActivationFactory() +template +HRESULT NotificationHelperWin::CreateActivationFactory( + wchar_t const (&class_name)[size], + T** object) const { + auto ref_class_name = base::win::ScopedHString::Create( + base::StringPiece16(class_name, size - 1)); + + return base::win::RoGetActivationFactory( + ref_class_name.get(), IID_PPV_ARGS(object)); } } // namespace brave_ads diff --git a/components/brave_ads/browser/notification_helper_win.h b/components/brave_ads/browser/notification_helper_win.h index b2884507375f..c8dc8e6b03a6 100644 --- a/components/brave_ads/browser/notification_helper_win.h +++ b/components/brave_ads/browser/notification_helper_win.h @@ -6,28 +6,45 @@ #ifndef BRAVE_COMPONENTS_BRAVE_ADS_BROWSER_NOTIFICATION_HELPER_WIN_H_ #define BRAVE_COMPONENTS_BRAVE_ADS_BROWSER_NOTIFICATION_HELPER_WIN_H_ -#include "base/macros.h" -#include "base/memory/singleton.h" +#include +#include + #include "base/memory/weak_ptr.h" #include "brave/components/brave_ads/browser/notification_helper.h" namespace brave_ads { -class NotificationHelperWin : - public NotificationHelper, - public base::SupportsWeakPtr { +class NotificationHelperWin + : public NotificationHelper, + public base::SupportsWeakPtr { public: + static NotificationHelperWin* GetInstanceImpl(); + + private: NotificationHelperWin(); ~NotificationHelperWin() override; - static NotificationHelperWin* GetInstance(); + bool IsFocusAssistEnabled() const; + + bool IsNotificationsEnabled(); + + base::string16 GetAppId() const; + + HRESULT InitializeToastNotifier(); + + template + HRESULT CreateActivationFactory( + wchar_t const (&class_name)[size], + T** object) const; + + Microsoft::WRL::ComPtr notifier_; - private: // NotificationHelper impl - bool ShouldShowNotifications() const override; + bool ShouldShowNotifications() override; - bool ShowMyFirstAdNotification() const override; + bool ShowMyFirstAdNotification() override; bool CanShowBackgroundNotifications() const override; diff --git a/components/brave_rewards/browser/rewards_service_browsertest.cc b/components/brave_rewards/browser/rewards_service_browsertest.cc index 0c4cc7cb93ee..eb6dc4a02f4b 100644 --- a/components/brave_rewards/browser/rewards_service_browsertest.cc +++ b/components/brave_rewards/browser/rewards_service_browsertest.cc @@ -30,6 +30,7 @@ #include "brave/components/brave_ads/browser/ads_service_impl.h" #include "brave/components/brave_ads/common/pref_names.h" #include "brave/components/brave_ads/browser/locale_helper_mock.h" +#include "brave/components/brave_ads/browser/notification_helper_mock.h" #include "chrome/browser/ui/browser.h" #include "chrome/browser/ui/views/frame/browser_view.h" #include "chrome/common/chrome_constants.h" @@ -162,6 +163,8 @@ class BraveRewardsBrowserTest // You can do set-up work for each test here MaybeMockLocaleHelper(); + + MockNotificationHelper(); } ~BraveRewardsBrowserTest() override { @@ -596,6 +599,20 @@ class BraveRewardsBrowserTest .WillByDefault(Return(locale)); } + void MockNotificationHelper() { + notification_helper_mock_ = + std::make_unique>(); + + brave_ads::NotificationHelper::GetInstance()->set_for_testing( + notification_helper_mock_.get()); + + // TODO(https://openradar.appspot.com/27768556): We must mock + // NotificationHelper::ShouldShowNotifications to return false as a + // workaround to UNUserNotificationCenter throwing an exception during tests + ON_CALL(*notification_helper_mock_, ShouldShowNotifications()) + .WillByDefault(Return(false)); + } + void MaybeMockUserProfilePreferencesForBraveAdsUpgradePath() { std::vector parameters; if (!GetUpgradePathParams(¶meters)) { @@ -1513,6 +1530,7 @@ class BraveRewardsBrowserTest brave_ads::AdsServiceImpl* ads_service_; std::unique_ptr locale_helper_mock_; + std::unique_ptr notification_helper_mock_; brave_rewards::Grant grant_; diff --git a/components/services/bat_ads/bat_ads_client_mojo_bridge.cc b/components/services/bat_ads/bat_ads_client_mojo_bridge.cc index 73aad44fd3d0..a29fdd0b8db8 100644 --- a/components/services/bat_ads/bat_ads_client_mojo_bridge.cc +++ b/components/services/bat_ads/bat_ads_client_mojo_bridge.cc @@ -214,7 +214,7 @@ void BatAdsClientMojoBridge::ShowNotification( bat_ads_client_->ShowNotification(info->ToJson()); } -bool BatAdsClientMojoBridge::ShouldShowNotifications() const { +bool BatAdsClientMojoBridge::ShouldShowNotifications() { if (!connected()) { return false; } diff --git a/components/services/bat_ads/bat_ads_client_mojo_bridge.h b/components/services/bat_ads/bat_ads_client_mojo_bridge.h index f42d3b4f7bf8..0500fbec5dc4 100644 --- a/components/services/bat_ads/bat_ads_client_mojo_bridge.h +++ b/components/services/bat_ads/bat_ads_client_mojo_bridge.h @@ -48,7 +48,7 @@ class BatAdsClientMojoBridge : public ads::AdsClient { void ShowNotification( std::unique_ptr info) override; - bool ShouldShowNotifications() const override; + bool ShouldShowNotifications() override; void CloseNotification( const std::string& id) override; diff --git a/test/BUILD.gn b/test/BUILD.gn index 631c7defab8b..8c91baf2fb1e 100644 --- a/test/BUILD.gn +++ b/test/BUILD.gn @@ -569,6 +569,8 @@ test("brave_browser_tests") { "//brave/components/brave_rewards/browser/rewards_service_browsertest.cc", "//brave/components/brave_ads/browser/locale_helper_mock.cc", "//brave/components/brave_ads/browser/locale_helper_mock.h", + "//brave/components/brave_ads/browser/notification_helper_mock.cc", + "//brave/components/brave_ads/browser/notification_helper_mock.h", ] deps += [ diff --git a/vendor/bat-native-ads/include/bat/ads/ads_client.h b/vendor/bat-native-ads/include/bat/ads/ads_client.h index 31ef9a5e2be5..e8a46e6d467f 100644 --- a/vendor/bat-native-ads/include/bat/ads/ads_client.h +++ b/vendor/bat-native-ads/include/bat/ads/ads_client.h @@ -128,7 +128,7 @@ class ADS_EXPORT AdsClient { // Should return |true| if notifications can be displayed; otherwise should // return |false| - virtual bool ShouldShowNotifications() const = 0; + virtual bool ShouldShowNotifications() = 0; // Should close a notification virtual void CloseNotification( diff --git a/vendor/bat-native-ads/src/bat/ads/internal/ads_client_mock.h b/vendor/bat-native-ads/src/bat/ads/internal/ads_client_mock.h index 8d027b628185..e92328cb366b 100644 --- a/vendor/bat-native-ads/src/bat/ads/internal/ads_client_mock.h +++ b/vendor/bat-native-ads/src/bat/ads/internal/ads_client_mock.h @@ -64,7 +64,7 @@ class MockAdsClient : public AdsClient { MOCK_CONST_METHOD0(IsForeground, bool()); - MOCK_CONST_METHOD0(ShouldShowNotifications, bool()); + MOCK_METHOD0(ShouldShowNotifications, bool()); MOCK_METHOD1(ShowNotification, void( std::unique_ptr info)); diff --git a/vendor/bat-native-ads/src/bat/ads/internal/ads_impl.cc b/vendor/bat-native-ads/src/bat/ads/internal/ads_impl.cc index c983d90cbafa..00ea1449b3f6 100644 --- a/vendor/bat-native-ads/src/bat/ads/internal/ads_impl.cc +++ b/vendor/bat-native-ads/src/bat/ads/internal/ads_impl.cc @@ -1430,7 +1430,7 @@ void AdsImpl::NotificationAllowedCheck( // 'Notification not made', { reason: 'notifications not presently allowed' // } - FailedToServeAd("Notifications not presently allowed"); + FailedToServeAd("Notifications not allowed"); return; } diff --git a/vendor/brave-ios/Ads/Generated/NativeAdsClient.h b/vendor/brave-ios/Ads/Generated/NativeAdsClient.h index e05e7dbb9b63..8743ba51a2c9 100644 --- a/vendor/brave-ios/Ads/Generated/NativeAdsClient.h +++ b/vendor/brave-ios/Ads/Generated/NativeAdsClient.h @@ -29,7 +29,7 @@ class NativeAdsClient : public ads::AdsClient { const std::vector GetUserModelLanguages() const override; void LoadUserModelForLanguage(const std::string & language, ads::OnLoadCallback callback) const override; void ShowNotification(std::unique_ptr info) override; - bool ShouldShowNotifications() const override; + bool ShouldShowNotifications() override; void CloseNotification(const std::string& id) override; void SetCatalogIssuers(std::unique_ptr info) override; void ConfirmAd(std::unique_ptr info) override; diff --git a/vendor/brave-ios/Ads/Generated/NativeAdsClient.mm b/vendor/brave-ios/Ads/Generated/NativeAdsClient.mm index 12c4c84ef8d7..74870e50f630 100644 --- a/vendor/brave-ios/Ads/Generated/NativeAdsClient.mm +++ b/vendor/brave-ios/Ads/Generated/NativeAdsClient.mm @@ -63,7 +63,7 @@ [bridge_ showNotification:std::move(info)]; } -bool NativeAdsClient::ShouldShowNotifications() const { +bool NativeAdsClient::ShouldShowNotifications() { return [bridge_ shouldShowNotifications]; }