Прослушиватель уведомлений: доступ ко всем уведомлениямNotification listener: Access all notifications

Прослушиватель уведомлений предоставляет доступ к уведомлениям пользователя.The notification listener provides access to a user's notifications. Смарт-часы и другие носимые устройства могут использовать прослушиватель уведомлений для отправки уведомлений с телефона на носимое устройство.Smartwatches and other wearables can use the notification listener to send the phone's notifications to the wearable device. Домашние приложения службы автоматизации могут использовать прослушиватель уведомлений для выполнения определенных действий при получении уведомлений, например для мигания огней при получении вызова.Home automation apps can use notification listener to perform specific actions when notifications are received, such as making the lights blink when you receive a call.

Важно!

Требуется юбилейное обновление. Обратите внимание, что для использования прослушивателя уведомлений необходим пакет SDK 14393 и сборка 14393 или более поздней версии.Requires Anniversary Update: You must target SDK 14393 and be running build 14393 or higher to use Notification Listener.

Важные API-интерфейсы: класс UserNotificationListener, класс UserNotificationChangedTriggerImportant APIs: UserNotificationListener class, UserNotificationChangedTrigger class

Включите прослушиватель, добавив функцию пользовательских уведомленийEnable the listener by adding the User Notification capability

Чтобы использовать прослушиватель уведомлений, необходимо добавить в манифест приложения функцию прослушивателя пользовательских уведомлений.To use the notification listener, you must add the User Notification Listener capability to your app manifest.

  1. В Visual Studio в проводнике решений дважды щелкните файл Package.appxmanifest, чтобы открыть конструктор манифестов.In Visual Studio, in the Solution Explorer, double click your Package.appxmanifest file to open the manifest designer.
  2. Откройте вкладку "Возможности".Open the Capabilities tab.
  3. Проверьте функцию Прослушиватель пользовательских уведомлений.Check the User Notification Listener capability.

Проверьте, поддерживается ли прослушивательCheck whether the listener is supported

Если ваше приложение поддерживает более ранние версии Windows 10, необходимо использовать для проверки класс ApiInformation.If your app supports older versions of Windows 10, you need to use the ApiInformation class to check whether the listener is supported. Если прослушиватель не поддерживается, не отправляйте никакие вызовы в API-интерфейсы прослушивателя.If the listener isn't supported, avoid executing any calls to the listener APIs.

if (ApiInformation.IsTypePresent("Windows.UI.Notifications.Management.UserNotificationListener"))
{
    // Listener supported!
}
 
else
{
    // Older version of Windows, no Listener
}

Запрос доступа к прослушивателюRequesting access to the listener

Поскольку прослушиватель разрешает доступ к уведомлениям пользователя, пользователи обязаны предоставить приложению разрешение на доступ к уведомлениям.Since the listener allows access to the user's notifications, users must give your app permission to access their notifications. Во время первого запуска приложения необходимо запросить доступ на использование прослушивателя уведомлений.During your app's first-run experience, you should request access to use the notification listener. При желании можно продемонстрировать предварительный пользовательский интерфейс, поясняющий, почему приложению требуется доступ к уведомлениям пользователя, прежде чем можно вызвать метод RequestAccessAsync, чтобы пользователь понял, почему они должны разрешить доступ.If you want, you can show some preliminary UI that explains why your app needs access to the user's notifications before you call RequestAccessAsync, so that the user understands why they should allow access.

// Get the listener
UserNotificationListener listener = UserNotificationListener.Current;
 
// And request access to the user's notifications (must be called from UI thread)
UserNotificationListenerAccessStatus accessStatus = await listener.RequestAccessAsync();
 
switch (accessStatus)
{
    // This means the user has granted access.
    case UserNotificationListenerAccessStatus.Allowed:
 
        // Yay! Proceed as normal
        break;
 
    // This means the user has denied access.
    // Any further calls to RequestAccessAsync will instantly
    // return Denied. The user must go to the Windows settings
    // and manually allow access.
    case UserNotificationListenerAccessStatus.Denied:
 
        // Show UI explaining that listener features will not
        // work until user allows access.
        break;
 
    // This means the user closed the prompt without
    // selecting either allow or deny. Further calls to
    // RequestAccessAsync will show the dialog again.
    case UserNotificationListenerAccessStatus.Unspecified:
 
        // Show UI that allows the user to bring up the prompt again
        break;
}

Пользователь может отозвать этот доступ в любой момент в настройках Windows.The user can revoke access at any time via Windows Settings. Поэтому приложение всегда должно проверять состояние доступа с помощью метода жетакцессстатус перед выполнением кода, использующего прослушиватель уведомлений.Therefore, your app should always check the access status via the GetAccessStatus method before executing code that uses the notification listener. Если пользователь отзывает доступ, API-вызовы будут автоматически завершаться сбоем, исключение создаваться не будет (например, API для получения всех уведомлений просто вернет пустой список).If the user revokes access, the APIs will silently fail rather than throwing an exception (for example, the API to get all notifications will simply return an empty list).

Доступ к уведомлениям пользователяAccess the user's notifications

Прослушиватель уведомлений позволяет получить список текущих уведомлений пользователя.With the notification listener, you can get a list of the user's current notifications. Просто вызовите метод GetNotificationsAsync и задайте тип уведомлений, которые требуется получить (в настоящее время поддерживается только один тип уведомлений — всплывающие уведомления).Simply call the GetNotificationsAsync method, and specify the type of notifications you want to get (currently, the only type of notifications supported are toast notifications).

// Get the toast notifications
IReadOnlyList<UserNotification> notifs = await listener.GetNotificationsAsync(NotificationKinds.Toast);

Отображение уведомленийDisplaying the notifications

Каждое уведомление представляется в виде класса UserNotification, который предоставляет информацию о приложении, из которого поступает уведомление, времени создания уведомления, идентификаторе уведомления и самом уведомлении.Each notification is represented as a UserNotification, which provides information about the app that the notification is from, the time the notification was created, the notification's ID, and the notification itself.

public sealed class UserNotification
{
    public AppInfo AppInfo { get; }
    public DateTimeOffset CreationTime { get; }
    public uint Id { get; }
    public Notification Notification { get; }
}

Свойство AppInfo предоставляет информацию, необходимую для отображения уведомления.The AppInfo property provides the info you need to display the notification.

Примечание

Рекомендуется окружить весь код для обработки одного уведомления в блок try/catch, если непредвиденное исключение произойдет при захвате отдельного уведомления.We recommend surrounding all your code for processing a single notification in a try/catch, in case an unexpected exception occurs when you are capturing a single notification. Не следует отказываться от отображения всех остальных уведомлений из-за проблемы с одним из них.You shouldn't completely fail to display other notifications just because of an issue with one specific notification.

// Select the first notification
UserNotification notif = notifs[0];
 
// Get the app's display name
string appDisplayName = notif.AppInfo.DisplayInfo.DisplayName;
 
// Get the app's logo
BitmapImage appLogo = new BitmapImage();
RandomAccessStreamReference appLogoStream = notif.AppInfo.DisplayInfo.GetLogo(new Size(16, 16));
await appLogo.SetSourceAsync(await appLogoStream.OpenReadAsync());

Содержимое самого уведомления, например текст уведомления, содержится в свойстве Уведомление.The content of the notification itself, such as the notification text, is contained in the Notification property. Это свойство содержит визуальную часть уведомления.This property contains the visual portion of the notification. (Если вы знакомы с отправкой уведомлений в Windows, вы заметите, что свойства Visual и Visual.Bindings в объекте Notification соответствуют тому, что отправляют разработчики при отображении уведомления).(If you are familiar with sending notifications on Windows, you will notice that the Visual and Visual.Bindings properties in the Notification object correspond to what developers send when popping a notification.)

Нужно искать всплывающую привязку (чтобы избежать ошибок в коде, необходимо убедиться, что привязка не равна null).We want to look for the toast binding (for error-proof code, you should check that the binding isn't null). Можно получить текстовые элементы из привязки.From the binding, you can obtain the text elements. Можно отобразить столько текстовых элементов, сколько нужно.You can choose to display as many text elements as you would like. (В идеале необходимо отобразить их все.) Текстовые элементы можно обрабатывать иначе: например, первый как текст заголовка, а последующие — как текст основной части.(Ideally, you should display them all.) You can choose to treat the text elements differently; for example, treat the first one as title text, and subsequent elements as body text.

// Get the toast binding, if present
NotificationBinding toastBinding = notif.Notification.Visual.GetBinding(KnownNotificationBindings.ToastGeneric);
 
if (toastBinding != null)
{
    // And then get the text elements from the toast binding
    IReadOnlyList<AdaptiveNotificationText> textElements = toastBinding.GetTextElements();
 
    // Treat the first text element as the title text
    string titleText = textElements.FirstOrDefault()?.Text;
 
    // We'll treat all subsequent text elements as body text,
    // joining them together via newlines.
    string bodyText = string.Join("\n", textElements.Skip(1).Select(t => t.Text));
}

Удаление конкретного уведомленияRemove a specific notification

Если носимое устройство или служба позволяет пользователю скрыть уведомления, можно удалить фактическое уведомление, чтобы пользователь позже не увидел его на телефоне или на ПК.If your wearable or service allows the user to dismiss notifications, you can remove the actual notification so the user doesn't see it later on their phone or PC. Просто укажите ИД уведомления (из объекта UserNotification), которое необходимо удалить:Simply provide the notification ID (obtained from the UserNotification object) of the notification you'd like to remove:

// Remove the notification
listener.RemoveNotification(notifId);

Удалить все уведомленияClear all notifications

Метод UserNotificationListener.ClearNotifications удаляет все уведомления пользователя.The UserNotificationListener.ClearNotifications method clears all the user's notifications. Используйте этот метод с осторожностью.Use this method with caution. Удалять все уведомления нужно лишь в том случае, если ваше носимое устройство или служба отображает ВСЕ уведомления.You should only clear all notifications if your wearable or service displays ALL notifications. Если носимое устройство или служба отображает только определенные уведомления, то когда пользователь нажимает кнопку "Очистить уведомления", он ожидает, что удалены будут лишь некоторые уведомления; если вызвать метод ClearNotifications, удалятся все уведомления, включая те, которые не отображались на носимом устройстве или в службе.If your wearable or service only displays certain notifications, when the user clicks your "Clear notifications" button, the user is only expecting those specific notifications to be removed; however, calling the ClearNotifications method would actually cause all the notifications, including ones that your wearable or service wasn't displaying, to be removed.

// Clear all notifications. Use with caution.
listener.ClearNotifications();

Фоновая задача для добавленного/скрытого уведомленияBackground task for notification added/dismissed

Стандартный способ включить прослушивание уведомлений в приложении — настроить фоновую задачу, чтобы вы знали, когда приложение было добавлено или скрыто, независимо от того, работает ли приложение в настоящее время.A common way to enable an app to listen to notifications is to set up a background task, so that you can know when a notification was added or dismissed regardless of whether your app is currently running.

Благодаря однопроцессной модели, добавленной в юбилейном обновлении, добавить фоновые задачи относительно просто.Thanks to the single process model added in the Anniversary Update, adding background tasks is fairly simple. Получив от приложения доступ к прослушивателю уведомлений и разрешение на выполнение фоновых задач, просто зарегистрируйте новую фоновую задачу в коде основного приложения и задайте класс UserNotificationChangedTrigger с помощью типа всплывающего уведомления.In your main app's code, after you have obtained the user's access to Notification Listener and obtained access to run background tasks, simply register a new background task, and set the UserNotificationChangedTrigger using the Toast notification kind.

// TODO: Request/check Listener access via UserNotificationListener.Current.RequestAccessAsync
 
// TODO: Request/check background task access via BackgroundExecutionManager.RequestAccessAsync
 
// If background task isn't registered yet
if (!BackgroundTaskRegistration.AllTasks.Any(i => i.Value.Name.Equals("UserNotificationChanged")))
{
    // Specify the background task
    var builder = new BackgroundTaskBuilder()
    {
        Name = "UserNotificationChanged"
    };
 
    // Set the trigger for Listener, listening to Toast Notifications
    builder.SetTrigger(new UserNotificationChangedTrigger(NotificationKinds.Toast));
 
    // Register the task
    builder.Register();
}

Затем в файле App.xaml.cs переопределите метод OnBackgroundActivated, если вы еще не сделали этого, и используйте оператор-переключатель в имени задачи, чтобы определить, какой из многочисленных триггеров фоновых задач был вызван.Then, in your App.xaml.cs, override the OnBackgroundActivated method if you haven't yet, and use a switch statement on the task name to determine which of your many background task triggers was invoked.

protected override async void OnBackgroundActivated(BackgroundActivatedEventArgs args)
{
    var deferral = args.TaskInstance.GetDeferral();
 
    switch (args.TaskInstance.Task.Name)
    {
        case "UserNotificationChanged":
            // Call your own method to process the new/removed notifications
            // The next section of documentation discusses this code
            await MyWearableHelpers.SyncNotifications();
            break;
    }
 
    deferral.Complete();
}

Фоновая задача — это просто напоминание, не предоставляющей никакой информации о том, какое уведомление было добавлено или удалено.The background task is simply a "shoulder tap": it doesn't provide any information about which specific notification was added or removed. При вызове фоновой задачи необходимо синхронизировать уведомления на носимом устройстве, чтобы они отражали уведомления на платформе.When your background task is triggered, you should sync the notifications on your wearable so that they reflect the notifications in the platform. В этом случае если фоновая задача завершится сбоем, уведомления на носимом устройстве можно будет восстановить при следующем выполнении фоновой задачи.This ensures that if your background task fails, notifications on your wearable can still be recovered the next time your background task executes.

SyncNotifications — Это метод, который вы реализуете; в следующем разделе показано, как это делать.SyncNotifications is a method you implement; the next section shows how.

Определение добавленных и удаленных уведомленийDetermining which notifications were added and removed

В вашем методе SyncNotifications необходимо вычислить разницу между текущей коллекцией уведомлений и уведомлениями на платформе, чтобы определить, какие уведомления были добавлены или удалены (синхронизируя уведомления с носимым устройством).In your SyncNotifications method, to determine which notifications have been added or removed (syncing notifications with your wearable), you have to calculate the delta between your current notification collection, and the notifications in the platform.

// Get all the current notifications from the platform
IReadOnlyList<UserNotification> userNotifications = await listener.GetNotificationsAsync(NotificationKinds.Toast);
 
// Obtain the notifications that our wearable currently has displayed
IList<uint> wearableNotificationIds = GetNotificationsOnWearable();
 
// Copy the currently displayed into a list of notification ID's to be removed
var toBeRemoved = new List<uint>(wearableNotificationIds);
 
// For each notification in the platform
foreach (UserNotification userNotification in userNotifications)
{
    // If we've already displayed this notification
    if (wearableNotificationIds.Contains(userNotification.Id))
    {
        // We want to KEEP it displayed, so take it out of the list
        // of notifications to remove.
        toBeRemoved.Remove(userNotification.Id);
    }
 
    // Otherwise it's a new notification
    else
    {
        // Display it on the Wearable
        SendNotificationToWearable(userNotification);
    }
}
 
// Now our toBeRemoved list only contains notification ID's that no longer exist in the platform.
// So we will remove all those notifications from the wearable.
foreach (uint id in toBeRemoved)
{
    RemoveNotificationFromWearable(id);
}

Событие переднего плана для добавленных/удаленных уведомленийForeground event for notification added/dismissed

Важно!

Известная ошибка. в сборках перед обновлением 17763/Октябрь 2018/версия 1809 событие переднего плана вызовет цикл ЦП и (или) не работает.Known issue: In builds before Build 17763 / October 2018 Update / Version 1809, The foreground event will cause a CPU loop and/or didn't work. Если вам необходима поддержка этих более ранних сборок, используйте вместо нее фоновую задачу.If you need support on those earlier builds, use the background task instead.

Можно также прослушивать уведомления от обработчика событий в памяти...You can also listen to notifications from an in-memory event handler...

// Subscribe to foreground event
listener.NotificationChanged += Listener_NotificationChanged;
 
private void Listener_NotificationChanged(UserNotificationListener sender, UserNotificationChangedEventArgs args)
{
    // Your code for handling the notification
}

Как устранить задержки в фоновой задачеHow to fix delays in the background task

При тестировании приложения можно заметить, что фоновая задача иногда задерживается и не запускается в течение нескольких минут.When testing your app, you might notice that the background task is sometimes delayed and doesn't trigger for several minutes. Чтобы устранить эту задержку, запросите у пользователя команду "Параметры системы" — > "> аккумулятора" > "Использование аккумулятора" в приложении, найдите свое приложение в списке, выберите его и установите для него значение "всегда разрешено в фоновом режиме".To fix the delay, prompt the user to go to the system settings -> System -> Battery -> Battery usage by app, find your app in the list, select it, and set it to be "Always allowed in background."После этого фоновая задача будет всегда запускаться в течение секунды после получения уведомления. After this, the background task should always be triggered within around a second of the notification being received.