ToastNotifier::Setting: Careful with non-UWP applications!

ToastNotifier::Setting: Careful with non-UWP applications!

Some WinRT apis are available for classic win32 applications without using desktop-bridge, in our case today: ToastNotifications.

Contrary to UWP/Desktop bridge apps, the behavior of these API are sometimes not really well documented (or not at all), it was my case very recently with the property ToastNotifier::Setting.

If you are not familiar with this property, here is the documentation:

Gets a value that tells you whether there is an app, user, or system block that prevents the display of a toast notification.

When toast notifications have been disabled at more than one level, this property value reflects the setting with the largest scope. Precedence is as follows, from largest scope to smallest:

  1. DisabledByManifest
  2. DisabledByGroupPolicy
  3. DisabledForUser
  4. DisabledForApplication.

This API is very useful but much unknown. It allows developers to predict if a ToastNotification will be displayed or not, even before creating it. To better understand why this API is interesting, it’s important to remind that ToastNotification.Show doesn’t return a result, the only way to know if a toast failed is asynchronously via the callback ToastNotification.Failed, returning some obscure undocumented error codes.

A lot of apps rely a little too much on toasts for their UX, but it’s important to know that they are not reliable at 100%. ToastNotification.Setting can be helpful to detect if you should use an alternative way to display the information you want to show.

Contrary to UWP apps and desktop-bridge apps, legacy win32 apps don’t have a manifest describing the app and the capabilities associated. The only way for the OS to know if these kind of apps use toasts or not (and create a setting associated to these apps in the OS settings), is to detect the first time they try to display a toast.

So what’s the issue?

If your application has never used toasts on a device before, the OS won’t have a setting associated to your app and won’t know if the user blocked or not toasts for your app.

In this particular case, the WRL api get_Setting will failed and return E_NOTFOUND, even worse, the C++/WinRT API will crash your app if you don’t manage std::set_terminate.

What’s the reason?

In my opinion, a wrong assumption in the implementation of get_Setting. The code probably looks like that (very simplified):

auto globalSetting = ToastSettingsManager->GetGlobalSetting();
if(globalSetting.IsBlocked)
{
  *setting = DisabledForUser;
  return S_OK;
}
var settingsForApp = ToastSettingsManager.GetForSpecificAppId(appId);
if(settingsForApp == null)
  return E_NOTFOUND; //BUG
else
{
  if(settingsForApp.IsBlocked)
  {
    *setting = DisabledForApp;
    return S_OK;
  }
}
*setting = DisabledForApp;
return S_OK;

The bug is line 11, the OS shouldn’t return E_NOTFOUND if no settings are associated to the app but ignore and continue to execute the function. If no settings are available, it means that the user had no way to block toasts for the app, so we should conclude that the app is allowed to pop toasts.

What’s the trap?

A best practice should be to check ToastNotifier.Setting before trying to display a toast, even the documentation ToastNotification.Failed is clear about that.

The most common failure that you’ll see when you raise a toast is a settings error, for instance a block on toast notifications for this user. We recommend that you call ToastNotifier.Setting before you call Show instead of handling that situation through this event.

Setting will crash because you have never displayed toasts before and… you can’t display toasts because Setting crashes… it’s a vicious circle. The worst in this case is that you may not see this bug during your development, the odds are very high that you had already displayed some toasts before writing the code verifying the value of ToastNotifier.Setting, the crash will be very complicated to detect and reproduce because you will need to run your app on a PC/VM on which your application has never been run before.

Other bug

Let’s have fun and let’s do one more little test: what’s the result of get_Settings when toasts are blocked for all apps and no toasts have been displayed before?

Let’s look at the priority list:

  1. DisabledByManifest
  2. DisabledByGroupPolicy
  3. DisabledForUser
  4. DisabledForApplication

In this particular case, DisabledForUser (toasts blocked for all apps, True in our case) is listed before DisabledForApplication (toasts blocked for our app), so the OS should first check if toasts are blocked for every apps even before even trying to access to the setting specific to the app, but it seems it’s not the case and crashes instead of returning DisabledForUser.

Comments are closed.