Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Introduce Register Overload for assets #2596

Merged
merged 6 commits into from
Jun 30, 2022
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 };
sharath2727 marked this conversation as resolved.
Show resolved Hide resolved

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();
sharath2727 marked this conversation as resolved.
Show resolved Hide resolved
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));
sharath2727 marked this conversation as resolved.
Show resolved Hide resolved

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)
sharath2727 marked this conversation as resolved.
Show resolved Hide resolved
{
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()));
sharath2727 marked this conversation as resolved.
Show resolved Hide resolved

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);
sharath2727 marked this conversation as resolved.
Show resolved Hide resolved

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.
sharath2727 marked this conversation as resolved.
Show resolved Hide resolved
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 @@ -1322,6 +1323,122 @@ bool VerifyIconPathExists_Unpackaged()
return true;
}

bool VerifyRegisterWithNullDisplayNameFail_Unpackaged()
sharath2727 marked this conversation as resolved.
Show resolved Hide resolved
{
// 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()
sharath2727 marked this conversation as resolved.
Show resolved Hide resolved
{
// 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\)" });
sharath2727 marked this conversation as resolved.
Show resolved Hide resolved
}
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 @@ -1380,6 +1497,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());
}
};
}