Universal App - Lock screen Tile/Badge 顯示與 Notification Queue
在<Universal App - 操作 Tiles>與<Universal App - 操作 Secondary Tiles>介紹了 Tile 與 Secondary tile 的操作。
該篇將針對 (1) 如何讓 App 支援 lock screen 顯示 Tile/Badge;(2) 搭配 Notification Queue 更新 Tile。
A. Lock screen 加入 App 的 tile 與更新。
參考<Lock screen overview (Windows Runtime apps)>的說明,大略的介紹一下 App 如何在 lock screen 時的運作方式。
在 lock screen 的呈現有三個目標:
- Prevents accidental sign-in attempts on touch devices
- Provides a personalized surface for the user
- Displays lightweight information to the user:
- Date and time
- Network and battery status
- Notifications from a limited set of apps
- 以上的資訊均是 lock screen 上預設呈現的內容,也是無法從設定中修改的。
那麼一般的 App 能加入什麼東西?
1. The app tile's current badge
2. The text from the app tile's most recent tile notification
3. Toast notifications
但不是每一個 App 都可以顯示 text,根據 Windows 與 Windows Phone 的設定一次只能選擇一個 App 當作要顯示詳細資訊,
其他像顯示 badge 或 notifications 的分別有 7 個 (Windows) 與有 5 個 (Windows Phone)。
那麼,怎麼樣的 App 應該要實作支援 lock screen 上的顯示呢?
1. App 是通訊、mail 或是一些可在鎖屏時顯示數量或符號標記讓用戶知道需要打開屏慕往下細讀的通知;
2. App 是需要顯示詳細資訊在鎖屏時,例如:行事曆;
3. 不建議放至 App 是那種播放音樂或是顯示非重要狀態的訊息;
更多的建議可以參考<Guidelines and checklist for tiles and badges>的說明。
往下便說明如果實作讓 App 支援 lock screen 的功能。
〉為 App 加入支援 lock screen 上呈現的功能:
step1. 在 Package.appxmanifest 宣告支援 Toast capable 與 Lock screen notifications,如下圖:
開啟 toast capable 這樣一來才能在 lock screen 下收到 toast notification。
在 lock screen notification 有二個選項:Badge 與 Badge and Tile Text;
選擇 Badge 那只能在設定中選擇被加入:
選擇 Badge and Tile Text 則可以被加入:
step2. 準備對應的 logo 圖片:
size 說明如下:
-
- Size: 24x24 pixels (for the 100% scale image),按照比例放大至對應的 size;
- Type: .png
- Color: monochrome white
- Transparency: any
如果你選擇的 lock screen notification 類型是:Badge and Tile Text,要多準備 Wide logo。
如果想要支援 Secondary Tile 也可以 lock screen 顯示與更新,則需指定 SecondaryTile.LockScreenBadgeLogo 的值,
如果要顯示文字與 Badge 則要給予 LockScreenDisplayBadgeAndTileText。
step3. App 需要宣告支援 Background Task (必要):
Background Task 需要是以下三種類型:
-
- Control Channel (Windows-only)
- Timer
- Push Notification
只要 App 要支援 lock screen 上的更新就一定要宣告一個 Background Task 來使用。例如:
step4. 在 App 中加入功能推薦用戶將 App 選擇加入支援 lock screen:
藉由 requestAccessAsync API 請求後會出現一個 dialog 讓用戶選擇是否將 App 加入 lock screen 的功能;
根據用戶選擇會得到一個列舉 BackgroundAccessStatus (但在 Windows Phone 有不同的意思),進一步了解用戶的選擇。
然而,這個方法只能用於判斷 primary tile,Secondary tile 的則需要用戶手動去加入。
另外,由於 lock screen 可能已經被塞滿可以放入的 App ,當請求這個 API 是用戶如果不選擇或出現確認視窗時又切換到其他 App,
那就有可能得到 Exception。詳細可參考<Windows.ApplicationModel.Background.BackgroundAccessStatus>的說明。
另外,Windows Store App 有一個 權限的視窗可以讓用戶手動開啟或關閉支援 lock screen 的功能,如下:
請求的範例程式如下:
private async void RequestLockScreenAccess_Click(object sender, RoutedEventArgs e)
{
BackgroundAccessStatus status = BackgroundAccessStatus.Unspecified;
try
{
status = await BackgroundExecutionManager.RequestAccessAsync();
}
catch (UnauthorizedAccessException)
{
// An access denied exception may be thrown if two requests are issued at the same time
// For this specific sample, that could be if the user double clicks "Request access"
}
switch (status)
{
case BackgroundAccessStatus.AllowedWithAlwaysOnRealTimeConnectivity:
rootPage.NotifyUser("This app is on the lock screen and has access to Always-On Real Time Connectivity.", NotifyType.StatusMessage);
break;
case BackgroundAccessStatus.AllowedMayUseActiveRealTimeConnectivity:
rootPage.NotifyUser("This app is on the lock screen and has access to Active Real Time Connectivity.", NotifyType.StatusMessage);
break;
case BackgroundAccessStatus.Denied:
rootPage.NotifyUser("This app is not on the lock screen.", NotifyType.StatusMessage);
break;
case BackgroundAccessStatus.Unspecified:
rootPage.NotifyUser("The user has not yet taken any action. This is the default setting and the app is not on the lock screen.", NotifyType.StatusMessage);
break;
default:
break;
}
}
查詢的範例程式如下:
private void QueryLockScreenAccess_Click(object sender, RoutedEventArgs e)
{
switch (BackgroundExecutionManager.GetAccessStatus())
{
case BackgroundAccessStatus.AllowedWithAlwaysOnRealTimeConnectivity:
rootPage.NotifyUser("This app is on the lock screen and has access to Always-On Real Time Connectivity.", NotifyType.StatusMessage);
break;
case BackgroundAccessStatus.AllowedMayUseActiveRealTimeConnectivity:
rootPage.NotifyUser("This app is on the lock screen and has access to Active Real Time Connectivity.", NotifyType.StatusMessage);
break;
case BackgroundAccessStatus.Denied:
rootPage.NotifyUser("This app is not on the lock screen.", NotifyType.StatusMessage);
break;
case BackgroundAccessStatus.Unspecified:
rootPage.NotifyUser("The user has not yet taken any action. This is the default setting and the app is not on the lock screen.", NotifyType.StatusMessage);
break;
default:
break;
}
}
幾個重要的元件如下:
協助 App 詢問是否可以被加入至 lock screen 的 app list 或是查詢 App 是否已經被加入至 lock screen 的集合中而且可以連接到 background activity。
主要方法如下:
Method | Description |
GetAccessStatus | Gets the ability of the calling lock screen app to perform background activity and update its badge. |
GetAccessStatus(String) | Gets the ability of a specific lock screen app to perform background activity and update its badge. String: The Package Relative Application ID (PRAID) of the app whose capabilities are being retrieved. The specified app must be in the same package as the calling app. |
RemoveAccess | Removes the calling app from the lock screen's apps list. |
RemoveAccess(String) | Removes a specific app from the lock screen's apps list. String: The Package Relative Application ID (PRAID) of the app whose capabilities are being retrieved. The specified app must be in the same package as the calling app. |
RequestAccessAsync | Requests access for an app to run background tasks. |
RequestAccessAsync(String) | Requests access for an app to run background tasks. String: The Package Relative Application ID (PRAID) of the app whose capabilities are being retrieved. The specified app must be in the same package as the calling app. |
在 Windows 該列舉值可指定 App 是否有能力執行 background activity 與呈現一個 tile 資訊在 lock screen 上。
在 Windows Phone 該列舉值只能表明一個 App 有能力執行 background activity。
在 Windows 呼叫 Windows.ApplicationModel.Background.BackgroundExecutionManager.requestAccessAsync 會得到一個 confirm dialog 讓用戶選擇允許或不允許讓 App
有能力連結 lock screen 。而得到的回傳值代表是的是用戶的選擇與 App 是否當前已被加入在 lock screen 中。
對於 Windows Phone 而言在在註冊任何 background tasks 之前就要成功地呼叫 Windows.ApplicationModel.Background.BackgroundExecutionManager.requestAccessAsync ,
但它不會出現任何的提示,但回傳值的用意與 Windows 不同。
Member | Value | Description |
Unspecified | unspecified | 0 |
Windows: The user has not selected "allow" or "don't allow" in the dialog box, or dismissed it without making a choice. Windows and Windows Phone: The app cannot perform background activity in this state. However, it can request permission from the user to do so through the RequestAccessAsync method. |
AllowedWithAlwaysOnRealTimeConnectivity | 1 |
Windows: The user chose "allow" in the dialog box. The app is added to the lock screen, can set up background tasks, and, if it has the capability, can use the real-time connectivity (RTC) broker. This means that the app can function while the device is in the connected standby state. After this value has been returned, subsequent calls to the RequestAccessAsync method do not present the dialog box to the user. Windows Phone: This value is not used. |
AllowedMayUseActiveRealTimeConnectivity | 2 |
Windows: The user chose "allow" in the dialog box. The app is added to the lock screen and can set up background tasks, but it cannot use the real-time connectivity (RTC) broker. This means that the app might not function while the device is in connected standby. Note that apps that do not specify RTC in their manifest will always demonstrate this behavior. After this value has been returned, subsequent calls to the RequestAccessAsync method do not present the dialog box to the user. Windows Phone: The app can register background tasks. This value must be received before any background task type can be registered. |
Denied | 3 |
Windows: The user chose "don't allow" in the dialog box. The app is not added to the lock screen. After this value has been returned, subsequent calls to the RequestAccessAsync method do not present the dialog box to the user. Windows Phone: The user has explicitly disabled background tasks for the application in Settings or the maximum number of background apps across the system has been reached. |
上述程式碼是參考<Lock screen apps sample>,相關更新與發送的訊息方式可詳細參考它。
B. 為 Tile 更新加上 notification queue。
參考<Using the notification queue (Windows Runtime apps)>的說明整理了幾個重點:
1. 開啟 notification queue 將允許 tile 可最多顯示 5 個 notifications 更新的循環;
2. 預設在 Start screen 上的 tile 一次只會被一個 tile notification 更新,直到下一個更新送來;
3. 如果開啟 notification queue,預設 Windows 最多在 queue 中保存 5 個 notifications;
4. notification queue 支援用在 local、scheduled、periodic、push notification;
5. queue 中的 tile 顯示順序是由系統自動控制,無法用程式控制的;
6. 預設,如果 queue 中已存在 5 個 notifications,當有新的 notification 抵達時將自動取代舊的;
=>藉由設定每一個 notification 的 Tags (屬於字串值) 將可以影響那一個 notification 要被取代;
=>當 Windows 收到 notification 時會用 Tag 去檢查是否有存在相同 Tag 的 notification,有則取代它;
如果沒有找到的話,則預設使用 先入先出 (FIFO) 的規則;
7. 預設開啟 notification queue 後,使用 locally notification (包括 scheduled notifications) 發送後 tile 不會更新;
主要是因為預設使用 FIFO 機制,如果要立即更新有感覺請使用 Tags 的指定更新;
詳細可參考<TileNotification>與<ScheduledTileNotification>。
8. 如果是 push notification 要使用 Tag 則需要在 Server 傳送 notification 至 WNS 時,在 request 的 header 中加入
X-WNS-Tag 指定識別 Tag。同理,當用戶離線時,Server 送至 WNS 的通知一樣只能最多保留 5 個 notification,
詳細的 push notification 使用機制請參考<Universal App - 整合 Windows Notification Service (WNS) for Server>。
9. 如果是 periodic notification update 要支援 notification queue 的話,需啟動 StartPeriodicUpdateBatch 方法。
由於 periodic notification 是用 URI 做為識別,所以要提供 5 個 URI 做為 queue 的 notification。
〉notification queue 搭配 locally notification 的使用方式:
step1:在 App 被 launched 的時候啟動 TileUpdaterManager 允許 App 做 Notification queue;
// 啟動 Notification queue
TileUpdateManager.CreateTileUpdaterForApplication().EnableNotificationQueue(true);
建議最好的在取得 push notification channel、請求更新 tile 之前。
step2:建立要更新的 Tile XML 並給予 Tile 一個 Tag 做為識別值;
private void OnSendFiveTileNotification(object sender, RoutedEventArgs e)
{
for (int i = 0; i < 5; i++)
{
// 發送 5 個不同的 tile notification,並且指定 tag
// 可以修改這個部分的邏輯來指定那一個 tag 的 tile notification 要更新
UpdateDefaultTile(String.Format("Tag-{0}", i),
new String[] {
String.Format("Text Field {0}", i),
String.Format("Text Field {0}", i+1),
String.Format("Text Field {0}", i+2),
String.Format("Text Field {0}", i+3)
});
}
}
public void UpdateDefaultTile(String tag, String[] text)
{
// 準備要更新 tile 的內容與圖示
String imgPath = "ms-appx:///Assets/Tiles/05.png";
String imgAlt = "default image";
Dictionary<String, String> imgCollection = new Dictionary<string, string>
{
{"img0","ms-appx:///Assets/Tiles/01.png"},
{"img1","ms-appx:///Assets/Tiles/02.png"},
{"img2","ms-appx:///Assets/Tiles/03.png"},
{"img3","ms-appx:///Assets/Tiles/04.png"},
};
// TileUpdater 清掉所有的 tile update 內容。
TileUpdateManager.CreateTileUpdaterForApplication().Clear();
// 準備要使用的 tile template
TileTemplateType[] types = new TileTemplateType[] {
TileTemplateType.TileSquare150x150PeekImageAndText01,
#if WINDOWS_APP
TileTemplateType.TileWide310x150PeekImage02,
TileTemplateType.TileSquare310x310ImageAndTextOverlay03
#endif
#if WINDOWS_PHONE_APP
TileTemplateType.TileWidePeekImageCollection02
#endif
};
XmlDocument tileDom = new XmlDocument();
foreach (var tileType in types)
{
// get template
XmlDocument tileTemplateDom = TileUpdateManager.GetTemplateContent(tileType);
// feed text elements
XmlNodeList textXmls = tileTemplateDom.GetElementsByTagName("text");
for (int i = 0; i < text.Length; i++)
{
if (textXmls.Length - 1 >= i)
{
textXmls[i].InnerText = text[i];
}
}
XmlNodeList tileImageAttributes = tileTemplateDom.GetElementsByTagName("image");
if (tileType != TileTemplateType.TileWidePeekImageCollection02)
{
((XmlElement)tileImageAttributes[0]).SetAttribute("src", imgPath);
((XmlElement)tileImageAttributes[0]).SetAttribute("alt", imgAlt);
}
else
{
for (int i = 0; i < imgCollection.Count; i++)
{
String key = String.Format("img{0}", i);
((XmlElement)tileImageAttributes[i]).SetAttribute("src", imgCollection[key]);
((XmlElement)tileImageAttributes[i]).SetAttribute("alt", key);
}
}
if (tileDom.HasChildNodes() == false)
{
tileDom = tileTemplateDom;
}
else
{
// 合併多個 tile template
IXmlNode node = tileDom.ImportNode(tileTemplateDom.GetElementsByTagName("binding").Item(0), true);
tileDom.GetElementsByTagName("visual").Item(0).AppendChild(node);
}
}
// 取得要求更新的 TileNotification
TileNotification tile = new TileNotification(tileDom);
// 指定 Tag
tile.Tag = tag;
// 取得 TileUpdater 發送更新
TileUpdateManager.CreateTileUpdaterForApplication().Update(tile);
}
上述有提到預設 notification queue 是使用 FIFO 的機制,所以在這邊給予 Tag 來加以識別;
需注意 queue 只是開啟緩存的功能,跟 periodic notification 的呈現不一樣。
[補充]
1. Badge notification 的操作:
Badge notification 可用於更新 Tile 右上角的數字或符號,Windows Store App 與 Windows Phone App 支援度不一樣。
重點類別如下:
其功能類似 TileUpdateManager 一樣可更新 main tile 與 secondary tiles,主要方法如下:
Method | Description |
CreateBadgeUpdaterForApplication | Creates and initializes a new instance of the BadgeUpdater, which lets you change the appearance or content of the badge on the calling app's tile. |
CreateBadgeUpdaterForApplication(String) | Creates and initializes a new instance of the BadgeUpdater for a specified app tile's badge, usually the tile of another app in the package. The BadgeUpdater lets you change the appearance or content of that badge. |
CreateBadgeUpdaterForSecondaryTile | Creates and initializes a new instance of the BadgeUpdater, which enables you to change the appearance or content of a badge on a secondary tile. The tile can belong to the calling app or any other app in the same package. |
GetTemplateContent | Gets the XML content of one of the predefined badge templates so that you can customize it for a badge update. |
程式範例:
public static void UpdateBadgeNotification(Int32 number, String msg)
{
// Get badge number
XmlDocument badgeXml = BadgeUpdateManager.GetTemplateContent(BadgeTemplateType.BadgeNumber);
XmlElement badgeElement = (XmlElement)badgeXml.SelectSingleNode("/badge");
badgeElement.SetAttribute("value", number.ToString());
// Get badge BadgeGlyph
//XmlDocument badgeXml = BadgeUpdateManager.GetTemplateContent(BadgeTemplateType.BadgeGlyph);
//XmlElement badgeElement = (XmlElement)badgeXml.SelectSingleNode("/badge");
//badgeElement.SetAttribute("value", msg);
BadgeNotification badge = new BadgeNotification(badgeXml);
BadgeUpdateManager.CreateBadgeUpdaterForApplication().Update(badge);
// clear badge notification
//BadgeUpdateManager.CreateBadgeUpdaterForApplication().Clear();
}
更多詳細可參考<How to clear a badge>、<How to send a glyph or numeric badge in a local notification>、<How to set up periodic notifications for badges>
與<How to update a badge through push notifications>。
2. 關於 periodic notification 可參考<Push and periodic notifications sample>。
======
以上是分享如何增加 notification queue 的功能以及如何支援在 lock screen 加入 app 定義的 tile/badge 更新。
tile notification 還有很多可以玩的東西,大家可以參考一下。謝謝。
References:
〉Lock screen overview (Windows Runtime apps)
〉Quickstart: Using the NotificationsExtensions library in your code
〉The tile template catalog (Windows Runtime apps)
〉How to use the notification queue with local notifications (XAML)
〉Quickstart: Showing tile and badge updates on the lock screen (Windows Runtime apps)
〉Troubleshooting tile, toast, and badge notifications
〉Guidelines for periodic notifications
〉Push and periodic notifications sample