From b13316e2f5b817f73c01ba25eb6eb33b62666976 Mon Sep 17 00:00:00 2001 From: Pavan Hullumane Date: Tue, 15 Mar 2022 16:02:09 -0700 Subject: [PATCH] =?UTF-8?q?Add=20Selfcontained=20checks=20and=20misc=20fix?= =?UTF-8?q?es=20for=20race=20condition=20in=20AppNoti=E2=80=A6=20(#2267)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add Selfcontained checks and misc fixes for race condition in AppNotificationManager::Register() * Address PR concerns --- .../AppNotificationManager.cpp | 50 +- dev/AppNotifications/AppNotificationManager.h | 4 + .../NotificationProperties.cpp | 4 +- .../PushNotificationCreateChannelResult.h | 7 +- .../PushNotificationManager.cpp | 80 ++- .../PushNotificationManager.h | 1 + .../PushNotificationReceivedEventArgs.cpp | 4 +- .../PushNotificationReceivedEventArgs.h | 4 +- .../PushNotificationUtility.h | 10 +- dev/PushNotifications/PushNotifications.idl | 9 +- .../PushNotifications-spec.md | 192 +++---- .../ToastNotifications-spec.md | 533 ------------------ .../PushNotificationsTestApp.vcxproj | 20 +- .../PushNotificationsTestApp/main.cpp | 13 +- test/TestApps/PushNotificationsTestApp/pch.h | 1 + 15 files changed, 216 insertions(+), 716 deletions(-) delete mode 100644 specs/ToastNotifications/ToastNotifications-spec.md diff --git a/dev/AppNotifications/AppNotificationManager.cpp b/dev/AppNotifications/AppNotificationManager.cpp index 00fa66d17d..a30ffa8550 100644 --- a/dev/AppNotifications/AppNotificationManager.cpp +++ b/dev/AppNotifications/AppNotificationManager.cpp @@ -20,6 +20,7 @@ #include #include #include +#include using namespace std::literals; @@ -95,6 +96,18 @@ namespace winrt::Microsoft::Windows::AppNotifications::implementation try { + { + auto lock{ m_lock.lock_exclusive() }; + THROW_HR_IF_MSG(HRESULT_FROM_WIN32(ERROR_OPERATION_IN_PROGRESS), m_registering, "Registration is in progress!"); + m_registering = true; + } + + auto registeringScopeExit{ wil::scope_exit([&]() + { + auto lock { m_lock.lock_exclusive() }; + m_registering = false; + }) }; + winrt::guid storedComActivatorGuid{ GUID_NULL }; if (!PushNotificationHelpers::IsPackagedAppScenario()) { @@ -105,8 +118,11 @@ namespace winrt::Microsoft::Windows::AppNotifications::implementation storedComActivatorGuid = RegisterComActivatorGuidAndAssets(); } - auto notificationPlatform{ PushNotificationHelpers::GetNotificationPlatform() }; - THROW_IF_FAILED(notificationPlatform->AddToastRegistrationMapping(m_processName.c_str(), m_appId.c_str())); + if (!WindowsAppRuntime::SelfContained::IsSelfContained()) + { + auto notificationPlatform{ PushNotificationHelpers::GetNotificationPlatform() }; + THROW_IF_FAILED(notificationPlatform->AddToastRegistrationMapping(m_processName.c_str(), m_appId.c_str())); + } } winrt::guid registeredClsid{ GUID_NULL }; @@ -139,6 +155,12 @@ namespace winrt::Microsoft::Windows::AppNotifications::implementation } } + // This assumes that the caller has taken an exclusive lock + void AppNotificationManager::UnregisterHelper() + { + m_notificationComActivatorRegistration.reset(); + } + void AppNotificationManager::Unregister() { HRESULT hr{ S_OK }; @@ -150,8 +172,15 @@ namespace winrt::Microsoft::Windows::AppNotifications::implementation try { auto lock{ m_lock.lock_exclusive() }; + THROW_HR_IF_MSG(HRESULT_FROM_WIN32(ERROR_OPERATION_IN_PROGRESS), m_registering, "Register or Unregister currently in progress!"); + m_registering = true; + auto scope_exit = wil::scope_exit( + [&] { + m_registering = false; + }); + THROW_HR_IF_MSG(HRESULT_FROM_WIN32(ERROR_NOT_FOUND), !m_notificationComActivatorRegistration, "Not Registered for App Notifications!"); - m_notificationComActivatorRegistration.reset(); + UnregisterHelper(); } catch (...) { @@ -170,10 +199,21 @@ namespace winrt::Microsoft::Windows::AppNotifications::implementation try { - Unregister(); + { + auto lock{ m_lock.lock_exclusive() }; + UnregisterHelper(); + THROW_HR_IF_MSG(HRESULT_FROM_WIN32(ERROR_OPERATION_IN_PROGRESS), m_registering, "Register or Unregister currently in progress!"); + m_registering = true; + } + + auto scope_exit = wil::scope_exit( + [&] { + auto lock{ m_lock.lock_exclusive() }; + m_registering = false; + }); // Remove any Registrations from the Long Running Process that are necessary for Cloud toasts - if (!PushNotificationHelpers::IsPackagedAppScenario()) + if (!PushNotificationHelpers::IsPackagedAppScenario() && !WindowsAppRuntime::SelfContained::IsSelfContained()) { auto notificationPlatform{ PushNotificationHelpers::GetNotificationPlatform() }; THROW_IF_FAILED(notificationPlatform->RemoveToastRegistrationMapping(m_processName.c_str())); diff --git a/dev/AppNotifications/AppNotificationManager.h b/dev/AppNotifications/AppNotificationManager.h index 1b47ddf5a3..1ff387f145 100644 --- a/dev/AppNotifications/AppNotificationManager.h +++ b/dev/AppNotifications/AppNotificationManager.h @@ -44,6 +44,9 @@ namespace winrt::Microsoft::Windows::AppNotifications::implementation // INotificationManagerDeserializer winrt::Windows::Foundation::IInspectable Deserialize(winrt::Windows::Foundation::Uri const& uri); private: + + void UnregisterHelper(); + wil::unique_com_class_object_cookie m_notificationComActivatorRegistration; wil::srwlock m_lock; winrt::event m_notificationHandlers; @@ -52,6 +55,7 @@ namespace winrt::Microsoft::Windows::AppNotifications::implementation wil::unique_event m_waitHandleForArgs; winrt::Microsoft::Windows::AppNotifications::AppNotificationActivatedEventArgs m_activatedEventArgs{ nullptr }; std::wstring m_appId; + bool m_registering{ false }; }; struct AppNotificationManagerFactory : winrt::implements diff --git a/dev/AppNotifications/NotificationProperties.cpp b/dev/AppNotifications/NotificationProperties.cpp index d12ad951fa..5fc14103f1 100644 --- a/dev/AppNotifications/NotificationProperties.cpp +++ b/dev/AppNotifications/NotificationProperties.cpp @@ -27,9 +27,7 @@ namespace Helpers NotificationProperties::NotificationProperties(winrt::AppNotification const& toastNotification) { // Extract payload and convert it from XML to a byte array - auto payload = toastNotification.Payload(); - - auto payloadAsSimpleString = Helpers::WideStringToUtf8String(payload.c_str()); + auto payloadAsSimpleString = Helpers::WideStringToUtf8String(toastNotification.Payload()); m_payload = wil::unique_cotaskmem_array_ptr(static_cast(CoTaskMemAlloc(payloadAsSimpleString.size())), payloadAsSimpleString.size()); THROW_IF_NULL_ALLOC(m_payload.get()); diff --git a/dev/PushNotifications/PushNotificationCreateChannelResult.h b/dev/PushNotifications/PushNotificationCreateChannelResult.h index aef4483d3d..1820d6b7f1 100644 --- a/dev/PushNotifications/PushNotificationCreateChannelResult.h +++ b/dev/PushNotifications/PushNotificationCreateChannelResult.h @@ -8,6 +8,7 @@ namespace winrt::Microsoft::Windows::PushNotifications::implementation { struct PushNotificationCreateChannelResult : PushNotificationCreateChannelResultT { + PushNotificationCreateChannelResult() = default; PushNotificationCreateChannelResult(Microsoft::Windows::PushNotifications::PushNotificationChannel const& channel, hresult const& extendedError, Microsoft::Windows::PushNotifications::PushNotificationChannelStatus const& status); Microsoft::Windows::PushNotifications::PushNotificationChannel Channel(); winrt::hresult ExtendedError(); @@ -19,9 +20,3 @@ namespace winrt::Microsoft::Windows::PushNotifications::implementation const Microsoft::Windows::PushNotifications::PushNotificationChannelStatus m_status; }; } -namespace winrt::Microsoft::Windows::PushNotifications::factory_implementation -{ - struct PushNotificationCreateChannelResult : PushNotificationCreateChannelResultT - { - }; -} diff --git a/dev/PushNotifications/PushNotificationManager.cpp b/dev/PushNotifications/PushNotificationManager.cpp index 51bb293347..5d2a08547e 100644 --- a/dev/PushNotifications/PushNotificationManager.cpp +++ b/dev/PushNotifications/PushNotificationManager.cpp @@ -127,6 +127,14 @@ namespace winrt::Microsoft::Windows::PushNotifications::implementation } CATCH_RETURN() + bool PushNotificationManager::IsSupported() + { + // Only scenarios that use the Background Infrastructure component of the OS support Push in the SelfContained case + static bool isSupported{ !WindowsAppRuntime::SelfContained::IsSelfContained() || PushNotificationHelpers::IsPackagedAppScenario() }; + return isSupported; + + } + winrt::Microsoft::Windows::PushNotifications::PushNotificationManager PushNotificationManager::Default() { THROW_HR_IF(E_NOTIMPL, !::Microsoft::Windows::PushNotifications::Feature_PushNotifications::IsEnabled()); @@ -158,29 +166,46 @@ namespace winrt::Microsoft::Windows::PushNotifications::implementation winrt::Windows::Foundation::IInspectable PushNotificationManager::Deserialize(winrt::Windows::Foundation::Uri const& uri) { - // Verify if the uri contains a background notification payload. - // Otherwise, we expect to process the notification in a background task. - for (auto const& pair : uri.QueryParsed()) + winrt::Microsoft::Windows::PushNotifications::PushNotificationReceivedEventArgs eventArgs{ nullptr }; + + // All packaged processes are triggered through COM via Long Running Process or the Background Infra OS component + if (AppModel::Identity::IsPackagedProcess()) { - if (pair.Name() == L"payload") + THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_TIMEOUT), !m_waitHandleForArgs.wait(c_receiveArgsTimeoutInMSec)); + auto lock{ m_lock.lock_shared() }; + THROW_HR_IF(E_UNEXPECTED, !m_backgroundTaskArgs); + eventArgs = m_backgroundTaskArgs; + } + else // The process was launched via ShellExecute and we need to parse the uri (Only unpackaged) + { + for (auto const& pair : uri.QueryParsed()) { - // Convert escaped components to its normal content - // from the conversion done in the LRP (see NotificationListener.cpp) - std::wstring payloadAsEscapedWstring{ pair.Value() }; - std::wstring payloadAsWstring{ winrt::Windows::Foundation::Uri::UnescapeComponent(payloadAsEscapedWstring) }; - return winrt::make(payloadAsWstring); + if (pair.Name() == L"payload") + { + // Convert escaped components to its normal content from the conversion done in the Long Running Process (see NotificationListener.cpp) + auto payloadAsWstring = winrt::Windows::Foundation::Uri::UnescapeComponent(pair.Value()); + eventArgs = winrt::make(payloadAsWstring); + } } + + THROW_HR_IF_NULL_MSG(E_UNEXPECTED, eventArgs, "Could not serialize payload from command line Uri!"); } - THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_TIMEOUT), !m_waitHandleForArgs.wait(c_receiveArgsTimeoutInMSec)); - THROW_HR_IF(E_UNEXPECTED, !m_backgroundTaskArgs); - return m_backgroundTaskArgs; + return eventArgs; } winrt::IAsyncOperationWithProgress PushNotificationManager::CreateChannelAsync(const winrt::guid remoteId) { THROW_HR_IF(E_NOTIMPL, !::Microsoft::Windows::PushNotifications::Feature_PushNotifications::IsEnabled()); + if (!IsSupported()) + { + co_return winrt::make( + nullptr, + E_FAIL, + PushNotificationChannelStatus::CompletedFailure); + } + auto strong = get_strong(); try @@ -257,8 +282,8 @@ namespace winrt::Microsoft::Windows::PushNotifications::implementation // registeredClsid is set to GUID_NULL for unpackaged applications THROW_IF_FAILED(notificationPlatform->RegisterLongRunningActivatorWithClsid(processName.c_str(), registeredClsid)); - std::wstring toastAppId{ RetrieveNotificationAppId() }; - THROW_IF_FAILED(notificationPlatform->AddToastRegistrationMapping(processName.c_str(), toastAppId.c_str())); + std::wstring appId{ RetrieveNotificationAppId() }; + THROW_IF_FAILED(notificationPlatform->AddToastRegistrationMapping(processName.c_str(), appId.c_str())); } auto channel{ winrt::make(channelInfo) }; @@ -331,6 +356,11 @@ namespace winrt::Microsoft::Windows::PushNotifications::implementation void PushNotificationManager::Register() { + if (!IsSupported()) + { + return; + } + winrt::hresult hr{ S_OK }; try { @@ -551,6 +581,11 @@ namespace winrt::Microsoft::Windows::PushNotifications::implementation void PushNotificationManager::Unregister() { + if (!IsSupported()) + { + return; + } + winrt::hresult hr{ S_OK }; try { @@ -621,6 +656,11 @@ namespace winrt::Microsoft::Windows::PushNotifications::implementation void PushNotificationManager::UnregisterAll() { + if (!IsSupported()) + { + return; + } + bool comActivatorRegistration{ false }; bool singletonForegroundRegistration{ false }; { @@ -697,6 +737,11 @@ namespace winrt::Microsoft::Windows::PushNotifications::implementation winrt::event_token PushNotificationManager::PushReceived(TypedEventHandler handler) { + if (!IsSupported()) + { + return winrt::event_token{}; + } + { auto lock{ m_lock.lock_shared() }; THROW_HR_IF_MSG(HRESULT_FROM_WIN32(ERROR_NOT_FOUND), m_comActivatorRegistration || m_singletonLongRunningSinkRegistration, "Must register event handlers before calling Register()."); @@ -709,8 +754,11 @@ namespace winrt::Microsoft::Windows::PushNotifications::implementation void PushNotificationManager::PushReceived(winrt::event_token const& token) noexcept { - auto lock{ m_lock.lock_exclusive() }; - m_foregroundHandlers.remove(token); + if (IsSupported()) + { + auto lock{ m_lock.lock_exclusive() }; + m_foregroundHandlers.remove(token); + } } IFACEMETHODIMP PushNotificationManager::InvokeAll(_In_ ULONG length, _In_ byte* payload, _Out_ BOOL* foregroundHandled) noexcept try diff --git a/dev/PushNotifications/PushNotificationManager.h b/dev/PushNotifications/PushNotificationManager.h index dea2ccf97c..c2ba190554 100644 --- a/dev/PushNotifications/PushNotificationManager.h +++ b/dev/PushNotifications/PushNotificationManager.h @@ -26,6 +26,7 @@ namespace winrt::Microsoft::Windows::PushNotifications::implementation { PushNotificationManager(); ~PushNotificationManager(); + static bool IsSupported(); static winrt::Microsoft::Windows::PushNotifications::PushNotificationManager Default(); static winrt::Windows::Foundation::IInspectable PushDeserialize(winrt::Windows::Foundation::Uri const& uri); void Register(); diff --git a/dev/PushNotifications/PushNotificationReceivedEventArgs.cpp b/dev/PushNotifications/PushNotificationReceivedEventArgs.cpp index a78824d3db..5b3b8589f7 100644 --- a/dev/PushNotifications/PushNotificationReceivedEventArgs.cpp +++ b/dev/PushNotifications/PushNotificationReceivedEventArgs.cpp @@ -48,7 +48,7 @@ namespace winrt::Microsoft::Windows::PushNotifications::implementation THROW_HR_IF(E_NOTIMPL, !::Microsoft::Windows::PushNotifications::Feature_PushNotifications::IsEnabled()); } - PushNotificationReceivedEventArgs::PushNotificationReceivedEventArgs(std::wstring const& payload) : + PushNotificationReceivedEventArgs::PushNotificationReceivedEventArgs(winrt::hstring const& payload) : m_rawNotificationPayload(BuildPayload(payload)), m_unpackagedAppScenario(true) { @@ -65,7 +65,7 @@ namespace winrt::Microsoft::Windows::PushNotifications::implementation return { payload, payload + (length * sizeof(uint8_t)) }; } - std::vector PushNotificationReceivedEventArgs::BuildPayload(std::wstring const& payload) + std::vector PushNotificationReceivedEventArgs::BuildPayload(winrt::hstring const& payload) { std::string payloadToSimpleString{ ::winrt::Microsoft::Windows::PushNotifications::Helpers::WideStringToUtf8String(payload) }; return { payloadToSimpleString.c_str(), payloadToSimpleString.c_str() + (payloadToSimpleString.length() * sizeof(uint8_t)) }; diff --git a/dev/PushNotifications/PushNotificationReceivedEventArgs.h b/dev/PushNotifications/PushNotificationReceivedEventArgs.h index 263c90a8e2..767f2b32fb 100644 --- a/dev/PushNotifications/PushNotificationReceivedEventArgs.h +++ b/dev/PushNotifications/PushNotificationReceivedEventArgs.h @@ -12,7 +12,7 @@ namespace winrt::Microsoft::Windows::PushNotifications::implementation PushNotificationReceivedEventArgs(winrt::Windows::ApplicationModel::Background::IBackgroundTaskInstance const& backgroundTask); PushNotificationReceivedEventArgs(winrt::Windows::Networking::PushNotifications::PushNotificationReceivedEventArgs const& args); - PushNotificationReceivedEventArgs(std::wstring const& payload); + PushNotificationReceivedEventArgs(winrt::hstring const& payload); PushNotificationReceivedEventArgs(byte* const& payload, ULONG const& length); @@ -26,7 +26,7 @@ namespace winrt::Microsoft::Windows::PushNotifications::implementation std::vector BuildPayload(winrt::Windows::Storage::Streams::IBuffer const& buffer); std::vector BuildPayload(byte* const& payload, ULONG const& length); - std::vector BuildPayload(std::wstring const& payload); + std::vector BuildPayload(winrt::hstring const& payload); std::vector m_rawNotificationPayload; diff --git a/dev/PushNotifications/PushNotificationUtility.h b/dev/PushNotifications/PushNotificationUtility.h index 55feec44f6..b64618295b 100644 --- a/dev/PushNotifications/PushNotificationUtility.h +++ b/dev/PushNotifications/PushNotificationUtility.h @@ -21,13 +21,13 @@ namespace winrt::Microsoft::Windows::PushNotifications::Helpers inline constexpr std::wstring_view c_argumentCommandString = L"Arguments"; inline constexpr std::wstring_view c_executableCommandString = L"Executable"; - inline std::string WideStringToUtf8String(_In_ std::wstring const& utf16string) + inline std::string WideStringToUtf8String(_In_ winrt::hstring const& utf16string) { int size = WideCharToMultiByte( CP_UTF8, 0, - utf16string.data(), - static_cast(utf16string.length()), + utf16string.c_str(), + static_cast(utf16string.size()), nullptr, 0, nullptr, @@ -41,8 +41,8 @@ namespace winrt::Microsoft::Windows::PushNotifications::Helpers size = WideCharToMultiByte( CP_UTF8, 0, - utf16string.data(), - static_cast(utf16string.length()), + utf16string.c_str(), + static_cast(utf16string.size()), &utf8string[0], size, nullptr, diff --git a/dev/PushNotifications/PushNotifications.idl b/dev/PushNotifications/PushNotifications.idl index d5c58f2dc5..aff6197164 100644 --- a/dev/PushNotifications/PushNotifications.idl +++ b/dev/PushNotifications/PushNotifications.idl @@ -58,11 +58,6 @@ namespace Microsoft.Windows.PushNotifications [feature(Feature_PushNotifications)] runtimeclass PushNotificationCreateChannelResult { - PushNotificationCreateChannelResult( - PushNotificationChannel channel, - HRESULT extendedError, - PushNotificationChannelStatus status); - // The Push channel associated with the Result. Valid only if status is CompletedSuccess. PushNotificationChannel Channel { get; }; @@ -76,6 +71,10 @@ namespace Microsoft.Windows.PushNotifications [feature(Feature_PushNotifications)] runtimeclass PushNotificationManager { + // Checks to see if the APIs are supported for the Desktop app + // Certain self-contained apps may not support Push Notification scenarios by design + static Boolean IsSupported(); + // Gets a Default instance of a PushNotificationManager static PushNotificationManager Default{ get; }; diff --git a/specs/PushNotifications/PushNotifications-spec.md b/specs/PushNotifications/PushNotifications-spec.md index 0a8e8c9091..3df0b65ab8 100644 --- a/specs/PushNotifications/PushNotifications-spec.md +++ b/specs/PushNotifications/PushNotifications-spec.md @@ -77,7 +77,7 @@ Link to the official Windows App SDK timeline can be found # Examples -## In this scenario, the process that Registers the Push Activator and the process specified as the COM server are the same +## In this scenario, we Register the Process for Push Notifications The code in Main would follow the pattern below: @@ -102,6 +102,9 @@ The code in Main would follow the pattern below: seconds). - It will subscribe to a In-memory Push event handler hanging off the PushNotificationManager component. +- The app in Self-Contained mode may not support Push due to API limitations. It is recommended + that the developer call IsSupported() and implement a custom socket if the feature is + unsupported. ```cpp int main() @@ -147,59 +150,66 @@ int main() } else if (kind == ExtendedActivationKind::Launch) // This indicates that the app is launching in the foreground { - // Register the AAD RemoteIdentifier for the App to receive Push - auto channelOperation { pushNotificationManager.CreateChannelAsync( - winrt::guid("F80E541E-3606-48FB-xxxx-118A3C5F41F4")) }; - - // Setup the inprogress event handler - channelOperation.Progress( - []( - IAsyncOperationWithProgress const& sender, - PushNotificationCreateChannelStatus const& args) - { - if (args.status == PushNotificationChannelStatus::InProgress) + if (PushNotificationManager::IsSupported()) + { + // Register the AAD RemoteIdentifier for the App to receive Push + auto channelOperation { pushNotificationManager.CreateChannelAsync( + winrt::guid("F80E541E-3606-48FB-xxxx-118A3C5F41F4")) }; + + // Setup the inprogress event handler + channelOperation.Progress( + []( + IAsyncOperationWithProgress const& sender, + PushNotificationCreateChannelStatus const& args) { - // This is basically a noop since it isn't really an error state - printf("The first channel request is still in progress! \n"); - } - else if (args.status == PushNotificationChannelStatus::InProgressRetry) + if (args.status == PushNotificationChannelStatus::InProgress) + { + // This is basically a noop since it isn't really an error state + printf("The first channel request is still in progress! \n"); + } + else if (args.status == PushNotificationChannelStatus::InProgressRetry) + { + LOG_HR_MSG( + args.extendedError, + "The channel request is in back-off retry mode because of a retryable error! Expect delays in acquiring it. RetryCount = %d", + args.retryCount); + } + }); + + winrt::event_token pushToken; + + // Setup the completed event handler + channelOperation.Completed( + [&]( + IAsyncOperationWithProgress const& sender, + AsyncStatus const asyncStatus) { - LOG_HR_MSG( - args.extendedError, - "The channel request is in back-off retry mode because of a retryable error! Expect delays in acquiring it. RetryCount = %d", - args.retryCount); - } - }); - - winrt::event_token pushToken; - - // Setup the completed event handler - channelOperation.Completed( - [&]( - IAsyncOperationWithProgress const& sender, - AsyncStatus const asyncStatus) + auto result { sender.GetResults() }; + if (result.Status() == PushNotificationChannelStatus::CompletedSuccess) + { + auto channelUri { result.Channel().Uri() }; + auto channelExpiry { result.Channel().ExpirationTime() }; + + // Persist the channelUri and Expiry in the App Service for subsequent Push operations + } + else if (result.Status() == PushNotificationChannelStatus::CompletedFailure) + { + LOG_HR_MSG(result.ExtendedError(), "We hit a critical non-retryable error with channel request!"); + } + }); + + // Draw window and other foreground UI stuff here + + auto result { channelOperation.GetResults() }; + if (result.Status() == PushNotificationChannelStatus::CompletedSuccess) { - auto result { sender.GetResults() }; - if (result.Status() == PushNotificationChannelStatus::CompletedSuccess) - { - auto channelUri { result.Channel().Uri() }; - auto channelExpiry { result.Channel().ExpirationTime() }; - - // Persist the channelUri and Expiry in the App Service for subsequent Push operations - } - else if (result.Status() == PushNotificationChannelStatus::CompletedFailure) - { - LOG_HR_MSG(result.ExtendedError(), "We hit a critical non-retryable error with channel request!"); - } - }); - - // Draw window and other foreground UI stuff here - - auto result { channelOperation.GetResults() }; - if (result.Status() == PushNotificationChannelStatus::CompletedSuccess) + // Maintain the channel object to retrieve Uri and ExpirationTime + auto channel { result.Channel() }; + } + } + else { - // Maintain the channel object to retrieve Uri and ExpirationTime - auto channel { result.Channel() }; + // App implements it's own custom socket here to receive messages from the cloud since Push APIs are unsupported. } } @@ -210,79 +220,6 @@ int main() } ``` -## In this scenario, the process that Registers the Push Trigger and the process specified as the COM server are different. - -This only works for packaged applications that support PushTrigger and ComActivator flags in -RegisterActivator. Out-of-process activation is not supported for unpackaged apps. - -Process A (Registration of the Push Trigger only): - -```cpp -int main() -{ - // Registers a Push Trigger with the Background Infra component - PushNotificationManager::Default().Register(); - - // Unregisters this app as the ComServer - PushNotificationManager::Default().Unregister(); - - // Some app code .... - - return 0; -} -``` - -Process B (Register the inproc COM server and handle the background activation): - -```cpp -int main() -{ - // Registers the current process as an InProc COM server - PushNotificationManager::Default().Register(); - - // Check to see if the WinMain activation is due to a Push Activator - auto args = AppInstance::GetCurrent().GetActivatedEventArgs(); - auto kind = args.Kind(); - - if (kind == ExtendedActivationKind::Push) - { - // Do Push processing stuff - } - - // Some code .... - - // Unregisters the inproc COM Activator - PushNotificationManager::Default().Unregister(); - return 0; -} -``` - -## Registration of the Push Activator for LOW IL apps like UWP (Inproc) - -The app will simply call into the Default implementation of PushNotificationActivationInfo for the -Registration Flow instead of the CLSID overload. - -```cpp -PushNotificationActivationInfo info(); -PushNotificationChannelManager::RegisterPushNotificationActivator(info); -``` - -To intercept the payload, OnBackgroundActivated will have to be implemented by the app. - -```cpp -sealed partial class App : Application -{ - ... - - protected override void OnBackgroundActivated(BackgroundActivatedEventArgs args) - { - base.OnBackgroundActivated(args); - IBackgroundTaskInstance taskInstance = args.TaskInstance; - DoYourBackgroundWork(taskInstance); - } -} -``` - # Remarks ## Registration @@ -435,11 +372,6 @@ namespace Microsoft.Windows.PushNotifications [feature(Feature_PushNotifications)] runtimeclass PushNotificationCreateChannelResult { - PushNotificationCreateChannelResult( - PushNotificationChannel channel, - HRESULT extendedError, - PushNotificationChannelStatus status); - // A PushNotificationChannel only exists Status when is CompletedSuccess. PushNotificationChannel Channel { get; }; @@ -453,6 +385,10 @@ namespace Microsoft.Windows.PushNotifications [feature(Feature_PushNotifications)] runtimeclass PushNotificationManager { + // Checks to see if the APIs are supported for the Desktop app + // Certain self-contained apps may not support Push Notification scenarios by design + static Boolean IsSupported(); + // Gets a Default instance of a PushNotificationManager static PushNotificationManager Default{ get; }; diff --git a/specs/ToastNotifications/ToastNotifications-spec.md b/specs/ToastNotifications/ToastNotifications-spec.md deleted file mode 100644 index 40d091e7a8..0000000000 --- a/specs/ToastNotifications/ToastNotifications-spec.md +++ /dev/null @@ -1,533 +0,0 @@ -# Toast Notifications in Windows App SDK - -# Background - -Toast Notifications are UI popups that are added that contain rich text, controls and images to display a message to the user. They are developer driven and originate from a target application that is installed by a user on the local device. It is not sufficient for a toast popup on the screen to simply be displayed to the user, it also needs to be actionable. For example, the user should be able to click on a toast popup to launch an app in the correct context. For example, a news article related toast is expected to launch the News app along with the article in question in the foreground. Another actionable scenario is for the user to actually interact with the contents of the toast popup like a UI control button. For example, a messaging app like Teams that displays a message from another user could have a "Respond To" textbox and button so that the user can directly respond to the message in the popup without having to launch the application. This scenario triggers a background process on behalf of the application (with no UI) which inturn processes the "Reply To" message and forwards the response to the other device. - -Toast messages could have a local or cloud based origin. In the case of local toasts, the message always originates from the app installed on the device. For cloud toasts, the message always originates from a remote application service that targets the locally installed app in question. - -Here is a visual representation of a Simple Toast with no interactive controls: - -![Toast Simple](https://docs.microsoft.com/en-us/windows/apps/design/shell/tiles-and-notifications/images/send-toast-02.png) - -Here is a visual representation of a Toast with simple button controls: - -![Toast Interactive 1](https://docs.microsoft.com/en-us/windows/apps/design/shell/tiles-and-notifications/images/adaptivetoasts-structure.jpg) - -Here is a visual representation of a Toast with a Message "Reply To" option: - -![Toast Interactive 2](https://docs.microsoft.com/en-us/windows/apps/design/shell/tiles-and-notifications/images/toast-notification.png) - -For more details see: - - -- [Toast Notification WinRT APIs](https://docs.microsoft.com/en-us/uwp/api/Windows.UI.Notifications.ToastNotification?view=winrt-20348) - Defines all the API constructs that we have for Toast Notifications in WinRT today using the Legacy Windows SDK. -- [Toast Notification UX Guidance using Toolkit](https://docs.microsoft.com/en-us/windows/apps/design/shell/tiles-and-notifications/toast-ux-guidance) - Defines the UX Guidance for developers who want to display toasts using the toolkit libraries. -- [Sending Local Toasts using C# using Toolkit](https://docs.microsoft.com/en-us/windows/apps/design/shell/tiles-and-notifications/send-local-toast?tabs=uwp) - Defines how an app developer can send interactive toasts to the user and setup activation handlers for these toasts using the Toolkit libraries. - -## The problems today - -**Cloud Toast integration with Windows SDK**: While we support Push scenarios for Raw notification on behalf of unpackaged applications in the Windows SDK 1.0, we do not support Cloud Toasts. We need to fill this gap for 1.1 and beyond. - -**API fragmentation**: There are too many API technologies that are published today to simply Display toasts and setup their activation handlers: Windows Legacy SDK, Win32 Toast Activations via the Nitro Model and the Windows Toolkit which is a Nuget library. It gets overwhelming for a developer who does not understand all these technologies to ramp up and get toast support working for their app. - -**Support and Troubleshooting Toast Issues**: Because of the API fragmentation mentioned above, it is also very difficult to diagnose issues when developers hit them and a large portion of developer cycles are spent dignosing and troubleshooting problems in various API stacks instead of a single stack. - -**Activation**: Setting up activation handlers using Background Toast Triggers is challenging for developers because we don't have downlevel support for all OS SKUs. Moreover, the steps for setting up activation handlers vary greatly for different app types MSIX Vs Unpackaged Vs UWP. We will need to abstract away the activation technology and simplify this process for developers for all supported downlevel OS SKUs. - -**Toast Composition**: There are well known gaps in the Legacy Windows SDK today in the area of composing the Toast content. The Windows Toolkit gets around this limitation by providing a well-defined object model to compose toasts but all developers don't use the Toolkit. - -# Description - -At a high level, we need to provide a way for all Win32 applications to display toasts irrespective of their app type. This includes unpackaged apps and packaged win32 -(MSIX Desktop Bridge, MSIX Win32App, Sparse Signed Packages). Moreover, all Toast scenarios should -adhere to OS resource management policies like Power Saver, Network Attribution (amount of data an -app uses), enterprise group policies, etc. The Windows App SDK Push component will abstract away the -complexities of dealing with Toast delivery and toast related activations as much as -possible freeing the developer to focus on other app related challenges. - -We will prioritize the following feature set for Toast Notifications in Windows App SDK: - -- Cloud sourced Toasts for Unpackaged Win32 apps. -- Toast Registrations for Unpackaged apps. -- Toast Registrations for Packaged apps -- Local Toast delivery and CRUD operations for Packaged Win32 apps. -- Local Toasts delivery and CRUD operations for unpackaged Win32 apps. - -The following feature set will not be prioritized for the next release but will be prioritized for the second half of 2022: - -- Object Model to compose Toasts without having to rely on Xml templates. -- Support for Granular toasts and toast collections. - -# Examples - -## Packaged App Toast Registration - -This scenario is specifically geared towards Packaged apps and toasts. An app Developer Registers their app for Toast Notifications and sets it up for background\foreground activation depending on the launch arguments. - -```cpp -int main() -{ - auto activationInfo = ToastActivationInfo::CreateToastActivationInfoFromGuid(winrt::guid(c_comServerId)); - ToastNotificationManager::RegisterActivator(activationInfo); - - auto args = AppInstance::GetCurrent().GetActivatedEventArgs(); - auto kind = args.Kind(); - - if (kind == ExtendedActivationKind::Launch) - { - // App is launched in FG. So intercept toast activators via FG event - const auto token = ToastNotificationManager::ToastActivated([](const auto&, ToastActivatedEventArgs const& toastActivatedEventArgs) - { - ProcessToastArgs(toastActivatedEventArgs); - }); - - // App does Foreground Stuff Here - - // Cleanup - ToastNotificationManager::ToastActivated(token); - } - else if (kind == ExtendedActivationKind::ToastNotification) - { - auto toastActivatedEventArgs = args.Data().as(); - ProcessToastArgs(toastActivatedEventArgs); - } - - return 0; -} -``` - -## Unpackaged app Toast Registration - -Similarly unpackaged apps can also Register themselves to send and act as targets for toast notifications. - -```cpp -int main() -{ - ToastDetails details = winrt::make( - L"SampleApp", - L"c:\test\icon.png", - Windows::UI::Colors::LightGray()); - auto activationInfo = ToastActivationInfo::CreateToastActivationInfoFromToastDetails(details); - ToastNotificationManager::RegisterActivator(activationInfo); - - auto args = AppInstance::GetCurrent().GetActivatedEventArgs(); - auto kind = args.Kind(); - - if (kind == ExtendedActivationKind::Launch) - { - // App is launched in FG. So intercept toast activators via FG event - const auto token = ToastNotificationManager::ToastActivated([](const auto&, ToastActivatedEventArgs const& toastActivatedEventArgs) - { - ProcessToastArgs(toastActivatedEventArgs); - }); - - // App does Foreground Stuff Here - - // Cleanup - ToastNotificationManager::ToastActivated(token); - } - else if (kind == ExtendedActivationKind::ToastNotification) - { - auto toastActivatedEventArgs = args.Data().as(); - ProcessToastArgs(toastActivatedEventArgs); - } - - return 0; -} -``` - -## Displaying a Toast - -The app will need to display a toast to the user. To do this we recommend that the developer always sets a tag and group field so that similar toasts can replace it in the future. They also have the option to delete the toast automatically from ActionCentre on subsequent reboots by setting the ExpiresOnReboot property to true. - -```cpp -void DisplayToast(winrt::Windows::Data::Xml::Dom::XmlDocument doc) -{ - ToastNotification toast = winrt::make(doc); - toast.Tag(L"Tag"); - toast.Group(L"Group"); - toast.ExpiresOnReboot(true); - ToastNotificationManager::ShowToast(toast); -} -``` - -## Processing a Toast Activation - -The app will need to process the activator in response to a User activating the toast. 2 common scenarios here are -1) Have the app launch in the foreground in a specific UI context OR -2) Have the app process a toast action (like a button press in the toast body) in the background. - -```cpp -void ProcessToastArgs(ToastActivatedEventArgs const& toastActivatedEventArgs) -{ - if (toastActivatedEventArgs.ActivationArgs() == c_toastLaunchAction) - { - // The user clicks on the toast: So use the launchAction to do stuff - // Do LaunchAction Stuff - } - else if (toastActivatedEventArgs.ActivationArgs() == c_toastReplyButtonAction) - { - // The user clicked on the reply button on the toast. So query the input field - auto input = toastActivatedEventArgs.UserInput(); - auto replyBoxText = input.Lookup(L"ReplyBox"); - - // Process the reply text - ProcessReply(replyBoxText); - } -} -``` -## Processing Toast History - -The ToastHistory component is always used to perform Delete and Get operations on behalf of the app. - -```cpp -// Get a List of all toasts from the Action Centre -winrt::Windows::Foundation::IAsyncOperation > GetToastListASync() -{ - auto toastHistory = ToastNotificationManager::History(); - auto toasts = co_await toastHistory.GetAllAsync(); - co_return toasts; -} - -// Remove a prior toast using tag and group -winrt::Windows::Foundation::IAsyncAction RemoveToast(const winrt::hstring& tag, const winrt::hstring& group) -{ - auto toastHistory = ToastNotificationManager::History(); - co_await toastHistory.RemoveWithTagGroupAsync(tag, group); -} -``` - -## Toast Progress Updates - -Sometimes a developer would like to show progress bar related updates in a toast like this. To accomplish that, the developer will need to use the ToastProgressData construct. - -```cpp -// Send first Toast Progress Update -void SendUpdatableToastWithProgress() -{ - winrt::hstring tag = L"weekly-playlist"; - winrt::hstring group = L"downloads"; - - const winrt::hstring payload = L"" - L"" - L"" - L"Downloading this week's new music..." - L"" - L"" - L"" - L""; - - winrt::Windows::Data::Xml::Dom::XmlDocument doc = winrt::make(); - doc.LoadXml(payload); - ToastNotification toast = winrt::make(doc); - toast.Tag(tag); - toast.Group(group); - - // Assign initial values for first toast progress UI - ToastProgressData data = winrt::make(); - data.Title(L"Weekly playlist"); // Binds to {progressTitle} in xml payload - data.Value(0.6); // Binds to {progressValue} in xml payload - data.ValueStringOverride(L"15/26 songs"); // Binds to {progressValueString} in xml payload - data.Status(L"Downloading..."); // Binds to {progressStatus} in xml payload - data.SequenceNumber(1); // The sequence number has to be incremental for the platform to render progress - - toast.Data(data); - ToastNotificationManager::ShowToast(toast); -} - -// Send subsequent progress updates -winrt::Windows::Foundation::IAsyncAction UpdateProgressAsync() -{ - winrt::hstring tag = L"weekly-playlist"; - winrt::hstring group = L"downloads"; - - // Assign new values - // Note that you only need to assign values that changed. In this example we don't assign progressStatus since we don't need to change it - ToastProgressData data = winrt::make(); - data.Value(0.7); // Binds to {progressValue} in xml payload - data.ValueStringOverride(L"18/26 songs"); // Binds to {progressValueString} in xml payload - data.SequenceNumber(2); - - auto result = co_await ToastNotificationManager::UpdateToastProgressDataAsync(data, tag, group); - if (result != ToastProgressResult::Succeeded) - { - LOG_HR_MSG(E_UNEXPECTED, "Toast Progress Update Failed!"); - } -} -``` - -# Remarks - -## Registration - -The developer should always call the Toast Registration API first to register the current process as the Activator target. - -## Foreground API calls - -The developer should always subscribe to toast activation foreground events to intercept toasts if the app happens to be running in the foreground. - -## Seperating Activator Registration flow from Channel Request flow - -We decided to have the following 2 APIs to be seperate calls instead of a single combined API call: - -```cpp -PushNotificationChannelManager::RegisterActivator(info) -PushNotificationChannelManager::CreatePushChannelAsync(remoteIdentifier) -``` - -Mainly for 2 reasons: - -- The app developer is expected to Register an activator for every WinMain app launch. Combining - the channel request API with the registration call would force the developer to keep the client - channel in sync with the App Service more frequently (both for foreground and background launch) - which can cause potential synchronization bugs. The preference is for developers to request new - channels only on Foreground launches triggered by the user. -- It isn't required that developers Register a Push Activator for Visual Toast operations. In the - case of Visual Toasts, payloads are directed to the Shell and not to the App. - -## Manifest Registration - -For MSIX, the COM activator GUID and the exe need to be registered in the manifest. The launch args -would need to be pre-set to a well-known string that defines Toast Activation Triggers. - -```xml - - - - - - - - - -``` - -# API Details - -```c# - -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. See LICENSE in the project root for license information. - -namespace Microsoft.Windows.ToastNotifications -{ - [experimental] - // The Shell asset details for Unpackaged App Registrations - runtimeclass ToastDetails - { - // Initialize using Shell assets like DisplayName and icons - ToastDetails(String displayName, String iconPath, Windows.UI.Colors iconBackgroundColor); - - // The App friendly DisplayName for the toast in Action Centre - String DisplayName { get; }; - - // The full file path for the icon image - String IconPath { get; }; - - // The icon color - Windows.UI.Colors IconBackgroundColor{ get; }; - } - - [experimental] - // The Registration Info for Packaged and Unpackaged Toast Activations - runtimeclass ToastActivationInfo - { - // Initialize using a manifest defined COM Activator Id. Only applicable to Packaged Win32 applications - static ToastActivationInfo CreateToastActivationInfoFromGuid(Guid taskClsid); - - // Initialize using an ActivationDetails context for Shell. Only applicable to Unpackaged Win32 applications which need to specify Shell assets. - static ToastActivationInfo CreateToastActivationInfoFromToastDetails(ToastDetails details); - - // The CLSID associated with the Client COM server that Windows App SDK will activate - Guid TaskClsid{ get; }; - - // The Shell assets associated with the Unpackaged app - ToastDetails Details{ get; }; - }; - - [experimental] - // Event args for the Toast Activation - runtimeclass ToastActivatedEventArgs - { - // Arguments from the invoked button. Empty for Default Activation with no launch args specified in payload. - String ActivationArgs{ get; }; - - // The data from the input elements of a toast like a TextBox - Windows.Foundation.Collections.IMapView UserInput{ get; }; - }; - - [experimental] - // Toast Progress Data - runtimeclass ToastProgressData - { - // Initializes a new Instance of ToastProgressData - ToastProgressData(); - - // Gets or sets the sequence number of this notification data. - // When multiple ToastProgressData objects are received, the system displays the data with the greatest non-zero number. - UInt32 SequenceNumber; - - // Gets/Sets the value for the title. Binds to {progressTitle} in progress xml tag. - String Title; - - // Gets/Sets the Value for the numerical Progress percentile: a number between 0 and 1. Binds to {progressValue} in progress xml tag. - Double Value; - - // Gets/Sets the Value for the Progress String. Binds to {progressValueString} in progress xml tag - String ValueStringOverride; - - // Gets/Sets the Value for the Status. Binds to {progressStatus} in progress xml tag - String Status; - }; - - [experimental] - [flags] - // The Toast User Setting or Toast Group Policy Setting - enum ToastNotificationSetting - { - Enabled, // Toast is not blocked by settings or group policy - DisabledForApplication, // Toast is blocked by a user defined App Setting - DisabledForUser, // Toast is blocked by a user defined Global Setting - DisabledByGroupPolicy, // Toast is blocked by Group Policy - DisabledByManifest, // Toast is blocked by a setting in the manifest. Only for packaged applications. - }; - - [experimental] - [flags] - // Some basic predefined Toast Payload Templates - enum ToastTemplateType - { - ToastImageAndText01, - ToastImageAndText02, - ToastImageAndText03, - ToastImageAndText04, - ToastText01, - ToastText02, - ToastText03, - ToastText04, - }; - - [experimental] - [flags] - // The Result for a Toast Progress related operation - enum ToastProgressResult - { - Succeeded, // The progress operation succeeded - Failed, // The progress operation failed - NotificationNotFound, // The progress operation failed to find a toast to process updates - }; - - [experimental] - [flags] - enum ToastPriority - { - Default, // The notification should have default behavior in terms of delivery and display priority during connected standby mode. - High, // The notification should be treated as high priority. For desktop PCs, this means during connected standby mode the incoming notification can turn on the screen for Surface-like devices if it doesn't have a closed lid detected. - }; - - [experimental] - // Represent a toast Notification construct - runtimeclass ToastNotification - { - // Initialize a new Toast using an XML Payload. - ToastNotification(Windows.Data.Xml.Dom.XmlDocument payload); - - // Unique identifier used to replace a notification within a group. - String Tag; - - // Unique identifier for a toast group in the app - String Group; - - // A unique identifier for the Toast generated by the platform. - UInt32 ToastIdentifier; - - // The notification Xml Payload - Windows.Data.Xml.Dom.XmlDocument Payload{ get; }; - - // Gets or sets additional information about the toast progress. - ToastProgressData Data; - - // Gets or sets the time after which a toast notification should not be displayed. - Windows.Foundation.DateTime ExpirationTime; - - // Indicates whether the toast will remain in the Action Center after a reboot. - Boolean ExpiresOnReboot; - - // Gets or sets the priority for a Toast. - // Hints on how and at what urgency level a notification should be presented to the user (whether to wake up the screen, etc). - ToastPriority Priority; - - // Gets or sets whether a toast's pop-up UI is displayed on the user's screen. - Boolean SuppressPopup; - }; - - [experimental] - // Supports Toast related operations for all prior displayed Toasts in Action Centre - runtimeclass ToastNotificationHistory - { - // Removes a specific toast with a specific toastIdentifier from Action Centre - Windows.Foundation.IAsyncAction RemoveWithIdentiferAsync(UInt32 toastIdentifier); - - // Removes a toast having a specific tag - Windows.Foundation.IAsyncAction RemoveWithTagAsync(String tag); - - // Removes a toast having a specific tag and group - Windows.Foundation.IAsyncAction RemoveWithTagGroupAsync(String tag, String group); - - // Remove all toasts for a specific group - Windows.Foundation.IAsyncAction RemoveGroupAllAsync(String group); - - // Removes all the toasts for the App from Action Centre - Windows.Foundation.IAsyncAction RemoveAllAsync(); - - // Gets all the toasts for the App from Action Centre - Windows.Foundation.IAsyncOperation > GetAllAsync(); - }; - - [experimental] - // The manager class which encompasses all Toast API Functionality - static runtimeclass ToastNotificationManager - { - // Register an activator using an ActivationInfo context and caches the token for unregister - static void RegisterActivator(ToastActivationInfo details); - - // Unregisters the activator and removes the cached Registration token. - static void UnregisterActivator(); - - // Event handler for Toast Activations - static event Windows.Foundation.EventHandler ToastActivated; - - // Displays the Toast in Action Centre - static void ShowToast(ToastNotification toast); - - // Updates the Toast for a Progress related operation using Tag and Group - static Windows.Foundation.IAsyncOperation UpdateToastProgressDataAsync(ToastProgressData data, String tag, String group); - - // Updates the Toast for a Progress related operation using Tag - static Windows.Foundation.IAsyncOperation UpdateToastProgressDataAsync(ToastProgressData data, String tag); - - // Get the Toast Setting status for the app - static Windows.Foundation.IAsyncOperation Setting{ get; }; - - // Gets an instance of ToastHistory - static ToastNotificationHistory History{ get; }; - - // Gets an Xml Payload based ona predefined Toast Template - static Windows.Data.Xml.Dom.XmlDocument GetXmlTemplateContent(ToastTemplateType type); - }; -} - -``` - -# Appendix - -- To support cloud toasts, the developer will need to Register the Toast Activator APIs. The Windows SDK will inturn figure out the complexity of mapping cloud based toasts with a specific Push Identifier to it's corresponding Toast Identifier. -- Since building Toast XML payloads during runtime is non-trivial, we encourage developers to utilize the windows developer toolkit, more specifically the ToastContentBuilder APIs to construct the xml payload. diff --git a/test/TestApps/PushNotificationsTestApp/PushNotificationsTestApp.vcxproj b/test/TestApps/PushNotificationsTestApp/PushNotificationsTestApp.vcxproj index 161d282614..cf1547a834 100644 --- a/test/TestApps/PushNotificationsTestApp/PushNotificationsTestApp.vcxproj +++ b/test/TestApps/PushNotificationsTestApp/PushNotificationsTestApp.vcxproj @@ -153,7 +153,7 @@ Use Level3 true - %(AdditionalIncludeDirectories);$(OutDir)\..\WindowsAppRuntime_DLL;$(OutDir)\..\WindowsAppRuntime_BootstrapDLL;..\..\inc + $(RepoRoot)\dev\Common;%(AdditionalIncludeDirectories);$(OutDir)\..\WindowsAppRuntime_DLL;$(OutDir)\..\WindowsAppRuntime_BootstrapDLL;..\..\inc WIN32;_DEBUG;%(PreprocessorDefinitions) pch.h stdcpp17 @@ -163,6 +163,7 @@ %(AdditionalLibraryDirectories);$(OutDir)\..\WindowsAppRuntime_DLL onecore.lib;onecoreuap.lib;Microsoft.WindowsAppRuntime.lib;%(AdditionalDependencies) Microsoft.WindowsAppRuntime.Bootstrap.dll;%(DelayLoadDLLs) + Microsoft.WindowsAppRuntime.dll;%(DelayLoadDLLs) @@ -172,7 +173,7 @@ true true true - %(AdditionalIncludeDirectories);$(OutDir)\..\WindowsAppRuntime_DLL;$(OutDir)\..\WindowsAppRuntime_BootstrapDLL;..\..\inc + $(RepoRoot)\dev\Common;%(AdditionalIncludeDirectories);$(OutDir)\..\WindowsAppRuntime_DLL;$(OutDir)\..\WindowsAppRuntime_BootstrapDLL;..\..\inc WIN32;NDEBUG;%(PreprocessorDefinitions) pch.h stdcpp17 @@ -184,6 +185,7 @@ %(AdditionalLibraryDirectories);$(OutDir)\..\WindowsAppRuntime_DLL onecore.lib;onecoreuap.lib;Microsoft.WindowsAppRuntime.lib;%(AdditionalDependencies) Microsoft.WindowsAppRuntime.Bootstrap.dll;%(DelayLoadDLLs) + Microsoft.WindowsAppRuntime.dll;%(DelayLoadDLLs) @@ -191,7 +193,7 @@ Use Level3 true - %(AdditionalIncludeDirectories);$(OutDir)\..\WindowsAppRuntime_DLL;$(OutDir)\..\WindowsAppRuntime_BootstrapDLL;..\..\inc + $(RepoRoot)\dev\Common;%(AdditionalIncludeDirectories);$(OutDir)\..\WindowsAppRuntime_DLL;$(OutDir)\..\WindowsAppRuntime_BootstrapDLL;..\..\inc _DEBUG;%(PreprocessorDefinitions) pch.h stdcpp17 @@ -201,6 +203,7 @@ %(AdditionalLibraryDirectories);$(OutDir)\..\WindowsAppRuntime_DLL onecore.lib;onecoreuap.lib;Microsoft.WindowsAppRuntime.lib;%(AdditionalDependencies) Microsoft.WindowsAppRuntime.Bootstrap.dll + Microsoft.WindowsAppRuntime.dll;%(DelayLoadDLLs) @@ -210,7 +213,7 @@ true true true - %(AdditionalIncludeDirectories);$(OutDir)\..\WindowsAppRuntime_DLL;$(OutDir)\..\WindowsAppRuntime_BootstrapDLL;..\..\inc + $(RepoRoot)\dev\Common;%(AdditionalIncludeDirectories);$(OutDir)\..\WindowsAppRuntime_DLL;$(OutDir)\..\WindowsAppRuntime_BootstrapDLL;..\..\inc NDEBUG;%(PreprocessorDefinitions) pch.h stdcpp17 @@ -222,6 +225,7 @@ %(AdditionalLibraryDirectories);$(OutDir)\..\WindowsAppRuntime_DLL onecore.lib;onecoreuap.lib;Microsoft.WindowsAppRuntime.lib;%(AdditionalDependencies) Microsoft.WindowsAppRuntime.Bootstrap.dll;%(DelayLoadDLLs) + Microsoft.WindowsAppRuntime.dll;%(DelayLoadDLLs) @@ -229,7 +233,7 @@ Use Level3 true - %(AdditionalIncludeDirectories);$(OutDir)\..\WindowsAppRuntime_DLL;$(OutDir)\..\WindowsAppRuntime_BootstrapDLL;..\..\inc + $(RepoRoot)\dev\Common;%(AdditionalIncludeDirectories);$(OutDir)\..\WindowsAppRuntime_DLL;$(OutDir)\..\WindowsAppRuntime_BootstrapDLL;..\..\inc _DEBUG;%(PreprocessorDefinitions) pch.h stdcpp17 @@ -239,6 +243,7 @@ %(AdditionalLibraryDirectories);$(OutDir)\..\WindowsAppRuntime_DLL onecore.lib;onecoreuap.lib;Microsoft.WindowsAppRuntime.lib;%(AdditionalDependencies) Microsoft.WindowsAppRuntime.Bootstrap.dll;%(DelayLoadDLLs) + Microsoft.WindowsAppRuntime.dll;%(DelayLoadDLLs) @@ -248,7 +253,7 @@ true true true - %(AdditionalIncludeDirectories);$(OutDir)\..\WindowsAppRuntime_DLL;$(OutDir)\..\WindowsAppRuntime_BootstrapDLL;..\..\inc + $(RepoRoot)\dev\Common;%(AdditionalIncludeDirectories);$(OutDir)\..\WindowsAppRuntime_DLL;$(OutDir)\..\WindowsAppRuntime_BootstrapDLL;..\..\inc NDEBUG;%(PreprocessorDefinitions) pch.h stdcpp17 @@ -260,6 +265,7 @@ %(AdditionalLibraryDirectories);$(OutDir)\..\WindowsAppRuntime_DLL onecore.lib;onecoreuap.lib;Microsoft.WindowsAppRuntime.lib;%(AdditionalDependencies) Microsoft.WindowsAppRuntime.Bootstrap.dll;%(DelayLoadDLLs) + Microsoft.WindowsAppRuntime.dll;%(DelayLoadDLLs) @@ -280,4 +286,4 @@ - + \ No newline at end of file diff --git a/test/TestApps/PushNotificationsTestApp/main.cpp b/test/TestApps/PushNotificationsTestApp/main.cpp index 4136536126..4ef8e9b10d 100644 --- a/test/TestApps/PushNotificationsTestApp/main.cpp +++ b/test/TestApps/PushNotificationsTestApp/main.cpp @@ -315,12 +315,17 @@ std::string unitTestNameFromLaunchArguments(const ILaunchActivatedEventArgs& lau int main() try { bool testResult = false; - auto scope_exit = wil::scope_exit([&] { - ::Test::Bootstrap::CleanupBootstrap(); - }); - ::Test::Bootstrap::SetupBootstrap(); + + // Test hook to ensure that the app is not self-contained + ::WindowsAppRuntime::SelfContained::TestInitialize(::Test::Bootstrap::TP::WindowsAppRuntimeFramework::c_PackageFamilyName); + + auto scope_exit = wil::scope_exit([&] { + ::WindowsAppRuntime::SelfContained::TestShutdown(); + ::Test::Bootstrap::CleanupBootstrap(); + }); + PushNotificationManager::Default().Register(); auto args = AppInstance::GetCurrent().GetActivatedEventArgs(); diff --git a/test/TestApps/PushNotificationsTestApp/pch.h b/test/TestApps/PushNotificationsTestApp/pch.h index e0081d9a95..233539b9fa 100644 --- a/test/TestApps/PushNotificationsTestApp/pch.h +++ b/test/TestApps/PushNotificationsTestApp/pch.h @@ -17,3 +17,4 @@ #include #include +#include