AskToPinPushPatternViewModel.cs 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Text;
  5. using System.ComponentModel.Composition;
  6. using System.Windows.Input;
  7. using WindowsPhone.Recipes.Push.Messasges;
  8. using WindowsPhone.Recipes.Push.Server.Services;
  9. using WindowsPhone.Recipes.Push.Server.Models;
  10. namespace WindowsPhone.Recipes.Push.Server.ViewModels
  11. {
  12. /// <summary>
  13. /// Represents the Ask user to Pin the application push notification pattern.
  14. /// </summary>
  15. /// <remarks>
  16. /// Only users can pin a Windows Phone application to the Start screen, therefore if a given
  17. /// application wants to use the tile functionality and the user didn’t pinned
  18. /// the app’s tile, we want to notify the user she is missing additional functionality.
  19. /// This pattern is implemented in both client and server-side.
  20. /// </remarks>
  21. [Export(typeof(PushPatternViewModel)), PartCreationPolicy(CreationPolicy.Shared)]
  22. internal sealed class AskToPinPushPatternViewModel : PushPatternViewModel
  23. {
  24. #region Fields
  25. /// <value>A dictionary for tracking tile messages.</value>
  26. private readonly Dictionary<string, TilePushNotificationMessage> _messages = new Dictionary<string, TilePushNotificationMessage>();
  27. /// <value>Synchronizes access to the messages collection.</value>
  28. private readonly object MessageSync = new object();
  29. #endregion
  30. #region Ctor
  31. /// <summary>
  32. /// Initialize new instance of this type with defaults.
  33. /// </summary>
  34. public AskToPinPushPatternViewModel()
  35. {
  36. InitializeDefaults();
  37. }
  38. #endregion
  39. #region Overrides
  40. /// <summary>
  41. /// Send a tile notification to all relevant subscribers.
  42. /// </summary>
  43. protected override void OnSend()
  44. {
  45. // Asynchronously try to send this message to all relevant subscribers.
  46. foreach (var subscriber in PushService.Subscribers)
  47. {
  48. // Create a tile message to send an update.
  49. var tileMsg = GetOrCreateMessage(subscriber.UserName);
  50. tileMsg.SendAsync(
  51. subscriber.ChannelUri,
  52. result =>
  53. {
  54. Log(result);
  55. OnMessageSent(subscriber.UserName, result);
  56. },
  57. Log);
  58. }
  59. }
  60. /// <summary>
  61. /// Once an application is activated again (the client side phone application
  62. /// has subscription logic on startup), try to update the tile again.
  63. /// In case that the application is not pinned, send raw notification message
  64. /// to the client, asking to pin the application. This raw notification message
  65. /// has to be well-known and handled by the client side phone application.
  66. /// In our case the raw message is AskToPin.
  67. /// </summary>
  68. protected override void OnSubscribed(SubscriptionEventArgs args)
  69. {
  70. // Asynchronously try to send Tile message to the relevant subscriber
  71. // with data already sent before so the tile won't change.
  72. var tileMsg = GetOrCreateMessage(args.Subscription.UserName, false);
  73. tileMsg.SendAsync(
  74. args.Subscription.ChannelUri,
  75. result =>
  76. {
  77. Log(result);
  78. OnMessageSent(args.Subscription.UserName, result);
  79. },
  80. Log);
  81. }
  82. #endregion
  83. #region Privates
  84. /// <summary>
  85. /// Once tile update sent, check if handled by the phone.
  86. /// In case that the application is not pinned, ask to pin.
  87. /// </summary>
  88. private void OnMessageSent(string userName, MessageSendResult result)
  89. {
  90. if (!CheckIfPinned(result))
  91. {
  92. AskUserToPin(result.ChannelUri);
  93. }
  94. }
  95. /// <summary>
  96. /// Just in case that the application is running, send a raw message, asking
  97. /// the user to pin the application. This raw message has to be handled in client side.
  98. /// </summary>
  99. private void AskUserToPin(Uri uri)
  100. {
  101. new RawPushNotificationMessage(MessageSendPriority.High)
  102. {
  103. RawData = Encoding.ASCII.GetBytes(RawMessage)
  104. }.SendAsync(uri, Log, Log);
  105. }
  106. private bool CheckIfPinned(MessageSendResult result)
  107. {
  108. // We known if the application is pinned by checking the following send result flags:
  109. return result.DeviceConnectionStatus == DeviceConnectionStatus.Connected &&
  110. result.SubscriptionStatus == SubscriptionStatus.Active &&
  111. result.NotificationStatus == NotificationStatus.Received;
  112. }
  113. private TilePushNotificationMessage GetOrCreateMessage(string userName, bool overrideExisting = true)
  114. {
  115. lock (MessageSync)
  116. {
  117. TilePushNotificationMessage message;
  118. if (!_messages.TryGetValue(userName, out message) || overrideExisting)
  119. {
  120. message = new TilePushNotificationMessage(MessageSendPriority.High)
  121. {
  122. BackgroundImageUri = BackgroundImageUri,
  123. Count = Count,
  124. Title = Title
  125. };
  126. _messages[userName] = message;
  127. }
  128. return message;
  129. }
  130. }
  131. private void InitializeDefaults()
  132. {
  133. DisplayName = "Ask to Pin";
  134. Description = "Only users can pin a Windows Phone application to the Start screen, therefore if a given application wants to use the tile functionality and the user didn’t pinned the app’s tile, we want to notify the user she is missing additional functionality. This pattern is implemented in both client and server-side.";
  135. BackgroundImageUri = TileImages.Length > 2 ? TileImages[2] : TileImages.FirstOrDefault();
  136. Count = 1;
  137. Title = "Game Update";
  138. RawMessage = "AskToPin";
  139. }
  140. #endregion
  141. }
  142. }