Skip to content

Commit

Permalink
Introduce Register Overload for assets (#2596)
Browse files Browse the repository at this point in the history
* Introduce Register Overload for assets

* Address comments

* Add contractversion to register APi

* Address comments

* Add more examples of register API usage
  • Loading branch information
sharath2727 authored Jun 30, 2022
1 parent 5c966bc commit 8a3f22e
Show file tree
Hide file tree
Showing 10 changed files with 281 additions and 6 deletions.
49 changes: 49 additions & 0 deletions dev/AppNotifications/AppNotificationManager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
#include <winrt/Windows.Foundation.Collections.h>
#include <WindowsAppRuntime.SelfContained.h>
#include <ShellLocalization.h>
#include <filesystem>

using namespace std::literals;

Expand Down Expand Up @@ -150,6 +151,54 @@ namespace winrt::Microsoft::Windows::AppNotifications::implementation
}
}

void AppNotificationManager::Register(hstring const& displayName, winrt::Windows::Foundation::Uri const& iconUri)
{
if (!IsSupported())
{
return;
}

HRESULT hr{ S_OK };

auto logTelemetry{ wil::scope_exit([&]() {
AppNotificationTelemetry::LogRegister(hr, m_appId);
}) };

try
{
THROW_HR_IF_MSG(E_ILLEGAL_METHOD_CALL, AppModel::Identity::IsPackagedProcess(), "Not applicable for packaged applications");

THROW_HR_IF(E_INVALIDARG, displayName.empty() || (iconUri == nullptr));

AppNotificationAssets assets{ ValidateAssets(displayName, iconUri.RawUri().c_str()) };

{
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 registeredClsid{ RegisterUnpackagedApp(assets) };

// Create event handle before COM Registration otherwise if a notification arrives will lead to race condition
m_waitHandleForArgs.create();

// Register the AppNotificationManager as a COM server for Shell to Activate and Invoke
RegisterComServer(registeredClsid);
}
catch (...)
{
hr = wil::ResultFromCaughtException();
throw;
}
}

void AppNotificationManager::RegisterComServer(winrt::guid const& registeredClsid)
{
auto lock{ m_lock.lock_exclusive() };
Expand Down
1 change: 1 addition & 0 deletions dev/AppNotifications/AppNotificationManager.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ namespace winrt::Microsoft::Windows::AppNotifications::implementation
static winrt::Microsoft::Windows::AppNotifications::AppNotificationManager Default();
static winrt::Windows::Foundation::IInspectable AppNotificationDeserialize(winrt::Windows::Foundation::Uri const& uri);
void Register();
void Register(hstring const& displayName, winrt::Windows::Foundation::Uri const& iconUri);
void Unregister();
void UnregisterAll();
static bool IsSupported();
Expand Down
9 changes: 9 additions & 0 deletions dev/AppNotifications/AppNotificationUtility.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -286,6 +286,15 @@ AppNotificationAssets Microsoft::Windows::AppNotifications::Helpers::GetAssets()
return assets;
}

AppNotificationAssets Microsoft::Windows::AppNotifications::Helpers::ValidateAssets(winrt::hstring const& displayName, std::filesystem::path const& iconFilePath)
{
winrt::check_bool(std::filesystem::exists(iconFilePath));

THROW_HR_IF_MSG(E_INVALIDARG, !IsIconFileExtensionSupported(iconFilePath), "Icon format not supported");

return AppNotificationAssets{ displayName.c_str(), iconFilePath.wstring() };
}

void Microsoft::Windows::AppNotifications::Helpers::RegisterAssets(std::wstring const& appId, std::wstring const& clsid, AppNotificationAssets const& assets)
{
wil::unique_hkey hKey;
Expand Down
2 changes: 2 additions & 0 deletions dev/AppNotifications/AppNotificationUtility.h
Original file line number Diff line number Diff line change
Expand Up @@ -65,4 +65,6 @@ namespace Microsoft::Windows::AppNotifications::Helpers
std::wstring GetDisplayNameBasedOnProcessName();

Microsoft::Windows::AppNotifications::ShellLocalization::AppNotificationAssets GetAssets();

Microsoft::Windows::AppNotifications::ShellLocalization::AppNotificationAssets ValidateAssets(winrt::hstring const& displayName, std::filesystem::path const& iconFilePath);
}
6 changes: 5 additions & 1 deletion dev/AppNotifications/AppNotifications.idl
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import "..\AppLifecycle\AppLifecycle.idl";

namespace Microsoft.Windows.AppNotifications
{
[contractversion(1)]
[contractversion(2)]
apicontract AppNotificationsContract {}

// Event args for the Notification Activation
Expand Down Expand Up @@ -123,6 +123,10 @@ namespace Microsoft.Windows.AppNotifications
// For Unpackaged apps, the caller process will be registered as the COM server. And assets like displayname and icon will be gleaned from Shell and registered as well.
void Register();

// Unpackaged Apps can call this API to register custom displayname and icon for AppNotifications and register themselves as a COM server.
[contract(AppNotificationsContract, 2)]
void Register(String displayName, Windows.Foundation.Uri iconUri);

// Unregisters the COM Service so that a subsequent activation will launch a new process
void Unregister();

Expand Down
8 changes: 3 additions & 5 deletions dev/AppNotifications/ShellLocalization.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,8 @@ void WriteHIconToPngFile(wil::unique_hicon const& hIcon, _In_ PCWSTR pszFileName
THROW_IF_FAILED(spStreamOut->Commit(STGC_DANGEROUSLYCOMMITMERELYTODISKCACHE));
}

bool IsIconFileExtensionSupported(std::filesystem::path const& iconFilePath)

bool Microsoft::Windows::AppNotifications::ShellLocalization::IsIconFileExtensionSupported(std::filesystem::path const& iconFilePath)
{
static PCWSTR c_supportedExtensions[]{ L".bmp", L".ico", L".jpg", L".png" };

Expand Down Expand Up @@ -258,10 +259,7 @@ HRESULT Microsoft::Windows::AppNotifications::ShellLocalization::DeleteIconFromC
std::path iconFilePath{ RetrieveLocalFolderPath() / (notificationAppId + c_pngExtension) };

// If DeleteFile returned FALSE, then deletion failed and we should return the corresponding error code.
if (DeleteFile(iconFilePath.c_str()) == FALSE)
{
THROW_HR(HRESULT_FROM_WIN32(GetLastError()));
}
RETURN_IF_WIN32_BOOL_FALSE(DeleteFileW(iconFilePath.c_str()));

return S_OK;
}
Expand Down
2 changes: 2 additions & 0 deletions dev/AppNotifications/ShellLocalization.h
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,6 @@ namespace Microsoft::Windows::AppNotifications::ShellLocalization
HRESULT RetrieveAssetsFromShortcut(_Out_ Microsoft::Windows::AppNotifications::ShellLocalization::AppNotificationAssets& assets) noexcept;

HRESULT DeleteIconFromCache() noexcept;

bool IsIconFileExtensionSupported(std::filesystem::path const& iconFilePath);
}
50 changes: 50 additions & 0 deletions specs/AppNotifications/AppNotifications-spec.md
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,53 @@ int main()
}
```

## Registering for App Notifications using assets

For Unpackaged applications, the developer can Register using a custom Display Name and Icon.
WinAppSDK will register the application and display these assets when an App Notification is received.
The developer should provide both the assets or not provide them at all. Icon provided by the developer
should be a valid supported format. The API supports the formats - png, bmp, jpg, ico. The icon
should reside on the local machine only otherwise the API throws an exception. For Packaged applications,
this API is not applicable and will throw an exception. Below are some examples of usage:

```cpp
int main()
{
auto manager = winrt::AppNotificationManager::Default();

std::wstring iconFilepath{ std::filesystem::current_path() / "icon.ico" };
winrt::hstring displayName{ L"AppNotifications" };

manager.Register(displayName, winrt::Windows::Foundation::Uri {iconFilepath});

// other app init and then message loop here

// Call Unregister() before exiting main so that subsequent invocations will launch a new process
manager.Unregister();
return 0;
}
```

```cpp
int main()
{
auto manager = winrt::AppNotificationManager::Default();

std::wstring iconFilepath{ std::filesystem::current_path() / "icon.ico" };

std::wstring displayName{};
wil::GetModuleFileNameExW(GetCurrentProcess(), nullptr, displayName);

manager.Register(displayName.c_str(), winrt::Windows::Foundation::Uri {iconFilepath});

// other app init and then message loop here

// Call Unregister() before exiting main so that subsequent invocations will launch a new process
manager.Unregister();
return 0;
}
```

## Displaying an App Notification

To display a Notification, an app needs to define a payload in xml. In the example below, the
Expand Down Expand Up @@ -451,6 +498,9 @@ namespace Microsoft.Windows.AppNotifications
// For Unpackaged apps, the caller process will be registered as the COM server. And assets like displayname and icon will be gleaned from Shell and registered as well.
void Register();

// For Unpackaged apps only, the caller process will be registered as the COM server.
void Register(String displayName, Windows.Foundation.Uri iconUri);

// Unregisters the COM Service so that a subsequent activation will launch a new process
void Unregister();

Expand Down
125 changes: 125 additions & 0 deletions test/TestApps/ToastNotificationsTestApp/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ namespace winrt
const std::wstring c_localWindowsAppSDKFolder{ LR"(\Microsoft\WindowsAppSDK\)" };
const std::wstring c_pngExtension{ LR"(.png)" };
const std::wstring c_appUserModelId{ LR"(TaefTestAppId)" };
const std::wstring c_iconFilepath{ std::filesystem::current_path() / "icon1.ico" };

bool BackgroundActivationTest() // Activating application for background test.
{
Expand Down Expand Up @@ -1324,6 +1325,122 @@ bool VerifyIconPathExists_Unpackaged()
return true;
}

bool VerifyRegisterWithNullDisplayNameFail_Unpackaged()
{
// Register is already called in main with an explicit appusermodelId
winrt::AppNotificationManager::Default().UnregisterAll();
try
{
winrt::AppNotificationManager::Default().Register(winrt::hstring{}, winrt::Uri{ c_iconFilepath });
}
catch (...)
{
return winrt::to_hresult() == E_INVALIDARG;
}

return false;
}

bool VerifyRegisterWithNullIconFail_Unpackaged()
{
// Register is already called in main with an explicit appusermodelId
winrt::AppNotificationManager::Default().UnregisterAll();
try
{
winrt::AppNotificationManager::Default().Register(L"AppNotificationApp", nullptr);
}
catch (...)
{
return winrt::to_hresult() == E_INVALIDARG;
}

return false;
}

bool VerifyRegisterWithNullDisplayNameAndNullIconFail_Unpackaged()
{
// Register is already called in main with an explicit appusermodelId
winrt::AppNotificationManager::Default().UnregisterAll();
try
{
winrt::AppNotificationManager::Default().Register(winrt::hstring{}, nullptr);
}
catch (...)
{
return winrt::to_hresult() == E_INVALIDARG;
}

return true;
}

bool VerifyShowToastWithCustomDisplayNameAndIcon_Unpackaged()
{
// Register is already called in main with an explicit appusermodelId
winrt::AppNotificationManager::Default().UnregisterAll();
try
{
winrt::AppNotificationManager::Default().Register(L"AppNotificationApp", winrt::Uri{ c_iconFilepath });

winrt::check_bool(VerifyShowToast_Unpackaged());
}
catch (...)
{
return false;
}

return true;
}

bool VerifyRegisterWithDisplayNameAndInvalidIconPathFail_Unpackaged()
{
// Register is already called in main with an explicit appusermodelId
winrt::AppNotificationManager::Default().UnregisterAll();
try
{
winrt::AppNotificationManager::Default().Register(L"AppNotificationApp", winrt::Uri{ LR"(C:\InvalidPath\)" });
}
catch (...)
{
return winrt::to_hresult() == HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND);
}

return false;
}

bool VerifyRegisterWithEmptyDisplayNameFail_Unpackaged()
{
// Register is already called in main with an explicit appusermodelId
winrt::AppNotificationManager::Default().UnregisterAll();
try
{
// hstring treats L"" as assigning nullptr
winrt::AppNotificationManager::Default().Register(L"", winrt::Uri{ c_iconFilepath });
}
catch (...)
{
return winrt::to_hresult() == E_INVALIDARG;
}

return false;
}

bool VerifyRegisterWithAssetsFail()
{
// Register is already called in main with an explicit appusermodelId
winrt::AppNotificationManager::Default().UnregisterAll();
try
{
// API fails for Packaged Scenario
winrt::AppNotificationManager::Default().Register(L"AppNotificationApp", winrt::Uri{ LR"(C:\InvalidPath\)" });
}
catch (...)
{
return winrt::to_hresult() == E_ILLEGAL_METHOD_CALL;
}

return false;
}

std::map<std::string, bool(*)()> const& GetSwitchMapping()
{
static std::map<std::string, bool(*)()> switchMapping = {
Expand Down Expand Up @@ -1382,6 +1499,14 @@ std::map<std::string, bool(*)()> const& GetSwitchMapping()
{ "VerifyToastProgressDataSequence0Fail", &VerifyToastProgressDataSequence0Fail },
{ "VerifyToastUpdateZeroSequenceFail_Unpackaged", &VerifyToastUpdateZeroSequenceFail_Unpackaged },
{ "VerifyIconPathExists_Unpackaged", &VerifyIconPathExists_Unpackaged},

{ "VerifyRegisterWithNullDisplayNameFail_Unpackaged", &VerifyRegisterWithNullDisplayNameFail_Unpackaged},
{ "VerifyRegisterWithNullIconFail_Unpackaged", &VerifyRegisterWithNullIconFail_Unpackaged},
{ "VerifyRegisterWithNullDisplayNameAndNullIconFail_Unpackaged", &VerifyRegisterWithNullDisplayNameAndNullIconFail_Unpackaged},
{ "VerifyShowToastWithCustomDisplayNameAndIcon_Unpackaged", &VerifyShowToastWithCustomDisplayNameAndIcon_Unpackaged},
{ "VerifyRegisterWithDisplayNameAndInvalidIconPathFail_Unpackaged", &VerifyRegisterWithDisplayNameAndInvalidIconPathFail_Unpackaged},
{ "VerifyRegisterWithEmptyDisplayNameFail_Unpackaged", &VerifyRegisterWithEmptyDisplayNameFail_Unpackaged},
{ "VerifyRegisterWithAssetsFail", &VerifyRegisterWithAssetsFail},
};

return switchMapping;
Expand Down
35 changes: 35 additions & 0 deletions test/ToastNotificationTests/APITests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -583,5 +583,40 @@ namespace Test::ToastNotifications
{
RunTestUnpackaged(L"VerifyIconPathExists_Unpackaged", testWaitTime());
}

TEST_METHOD(VerifyRegisterWithNullDisplayNameFail_Unpackaged)
{
RunTestUnpackaged(L"VerifyRegisterWithNullDisplayNameFail_Unpackaged", testWaitTime());
}

TEST_METHOD(VerifyRegisterWithNullIconFail_Unpackaged)
{
RunTestUnpackaged(L"VerifyRegisterWithNullIconFail_Unpackaged", testWaitTime());
}

TEST_METHOD(VerifyRegisterWithNullDisplayNameAndNullIconFail_Unpackaged)
{
RunTestUnpackaged(L"VerifyRegisterWithNullDisplayNameAndNullIconFail_Unpackaged", testWaitTime());
}

TEST_METHOD(VerifyShowToastWithCustomDisplayNameAndIcon_Unpackaged)
{
RunTestUnpackaged(L"VerifyShowToastWithCustomDisplayNameAndIcon_Unpackaged", testWaitTime());
}

TEST_METHOD(VerifyRegisterWithDisplayNameAndInvalidIconPathFail_Unpackaged)
{
RunTestUnpackaged(L"VerifyRegisterWithDisplayNameAndInvalidIconPathFail_Unpackaged", testWaitTime());
}

TEST_METHOD(VerifyRegisterWithEmptyDisplayNameFail_Unpackaged)
{
RunTestUnpackaged(L"VerifyRegisterWithEmptyDisplayNameFail_Unpackaged", testWaitTime());
}

TEST_METHOD(VerifyRegisterWithAssetsFail)
{
RunTest(L"VerifyRegisterWithAssetsFail", testWaitTime());
}
};
}

0 comments on commit 8a3f22e

Please sign in to comment.