notification_presenter.mm 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173
  1. // Copyright 2019 The Chromium Authors. All rights reserved.
  2. // Use of this source code is governed by a BSD-style license that can be
  3. // found in the LICENSE file.
  4. #include "remoting/ios/app/notification_presenter.h"
  5. #import <MaterialComponents/MaterialDialogs.h>
  6. #import "remoting/ios/app/notification_dialog_view_controller.h"
  7. #import "remoting/ios/facade/remoting_authentication.h"
  8. #import "remoting/ios/facade/remoting_service.h"
  9. #import "remoting/ios/persistence/remoting_preferences.h"
  10. #include "base/bind.h"
  11. #include "base/check_op.h"
  12. #include "base/no_destructor.h"
  13. #include "base/strings/sys_string_conversions.h"
  14. #include "base/threading/sequenced_task_runner_handle.h"
  15. #include "remoting/base/string_resources.h"
  16. #include "remoting/client/chromoting_client_runtime.h"
  17. #include "remoting/ios/app/view_utils.h"
  18. #include "ui/base/l10n/l10n_util.h"
  19. #if !defined(__has_feature) || !__has_feature(objc_arc)
  20. #error "This file requires ARC support."
  21. #endif
  22. namespace remoting {
  23. namespace {
  24. // This is to make sure notification shows up after the user status toast (see
  25. // UserStatusPresenter).
  26. // TODO(yuweih): Chain this class with UserStatusPresenter on the
  27. // kUserDidUpdate event.
  28. constexpr base::TimeDelta kFetchNotificationDelay = base::Seconds(2);
  29. // The "Don't show again" checkbox only appears if the notification has been
  30. // shown for at least |kTimesShownRequiredToAllowSilencing| times. Note that
  31. // this means the checkbox shows up starting from the
  32. // (|kTimesShownRequiredToAllowSilencing| + 1)th app launch.
  33. constexpr unsigned int kTimesShownRequiredToAllowSilencing = 2u;
  34. enum NotificationUiState : unsigned int {
  35. NOT_SHOWN = 0,
  36. SHOWN = 1,
  37. SILENCED = 2,
  38. };
  39. NotificationUiState NSNumberToUiState(NSNumber* number) {
  40. return number ? static_cast<NotificationUiState>(number.unsignedIntValue)
  41. : NOT_SHOWN;
  42. }
  43. NSNumber* UiStateToNSNumber(NotificationUiState state) {
  44. return @(static_cast<unsigned int>(state));
  45. }
  46. } // namespace
  47. // static
  48. NotificationPresenter* NotificationPresenter::GetInstance() {
  49. static base::NoDestructor<NotificationPresenter> instance;
  50. return instance.get();
  51. }
  52. NotificationPresenter::NotificationPresenter()
  53. : notification_client_(
  54. ChromotingClientRuntime::GetInstance()->network_task_runner()) {}
  55. void NotificationPresenter::Start() {
  56. DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  57. DCHECK(!user_update_observer_);
  58. FetchNotification();
  59. user_update_observer_ = [NSNotificationCenter.defaultCenter
  60. addObserverForName:kUserDidUpdate
  61. object:nil
  62. queue:nil
  63. usingBlock:^(NSNotification*) {
  64. // This implicitly captures |this|, but should be fine since
  65. // |NotificationPresenter| is singleton.
  66. FetchNotification();
  67. }];
  68. }
  69. void NotificationPresenter::FetchNotification() {
  70. DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  71. if (state_ == State::FETCHED) {
  72. return;
  73. }
  74. UserInfo* user = RemotingService.instance.authentication.user;
  75. std::string user_email = [user isAuthenticated]
  76. ? base::SysNSStringToUTF8(user.userEmail)
  77. : std::string();
  78. state_ = State::FETCHING;
  79. // If FetchNotification() is called when the timer is already running, this
  80. // will restart the timer with the new user email.
  81. fetch_notification_timer_.Start(
  82. FROM_HERE, kFetchNotificationDelay,
  83. base::BindOnce(
  84. [](NotificationPresenter* that, const std::string& user_email) {
  85. that->notification_client_.GetNotification(
  86. user_email,
  87. base::BindOnce(&NotificationPresenter::OnNotificationFetched,
  88. base::Unretained(that)));
  89. },
  90. base::Unretained(this), user_email));
  91. }
  92. void NotificationPresenter::OnNotificationFetched(
  93. absl::optional<NotificationMessage> notification) {
  94. DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  95. DCHECK_EQ(State::FETCHING, state_);
  96. if (!notification) {
  97. // No notification for this user. Need to check again when user changes.
  98. state_ = State::NOT_FETCHED;
  99. return;
  100. }
  101. state_ = State::FETCHED;
  102. DCHECK(!notification->message_id.empty());
  103. std::string last_seen_message_id =
  104. base::SysNSStringToUTF8([RemotingPreferences.instance
  105. objectForFlag:RemotingFlagLastSeenNotificationMessageId]);
  106. NotificationUiState ui_state = NSNumberToUiState([RemotingPreferences.instance
  107. objectForFlag:RemotingFlagNotificationUiState]);
  108. if (notification->allow_silence && ui_state == SILENCED &&
  109. last_seen_message_id == notification->message_id) {
  110. return;
  111. }
  112. if (last_seen_message_id != notification->message_id) {
  113. [RemotingPreferences.instance
  114. setObject:base::SysUTF8ToNSString(notification->message_id)
  115. forFlag:RemotingFlagLastSeenNotificationMessageId];
  116. ui_state = NOT_SHOWN;
  117. [RemotingPreferences.instance setObject:UiStateToNSNumber(ui_state)
  118. forFlag:RemotingFlagNotificationUiState];
  119. [RemotingPreferences.instance setObject:@(0u)
  120. forFlag:RemotingFlagNotificationShownTimes];
  121. [RemotingPreferences.instance synchronizeFlags];
  122. }
  123. NSNumber* notificationShownTimesNsNumber = [RemotingPreferences.instance
  124. objectForFlag:RemotingFlagNotificationShownTimes];
  125. unsigned int notification_shown_times =
  126. notificationShownTimesNsNumber
  127. ? notificationShownTimesNsNumber.unsignedIntValue
  128. : 0u;
  129. BOOL allowSilence =
  130. notification->allow_silence && ui_state == SHOWN &&
  131. notification_shown_times >= kTimesShownRequiredToAllowSilencing;
  132. NotificationDialogViewController* dialogVc =
  133. [[NotificationDialogViewController alloc]
  134. initWithNotificationMessage:*notification
  135. allowSilence:allowSilence];
  136. [dialogVc presentOnTopVCWithCompletion:^(BOOL isSilenced) {
  137. NotificationUiState new_ui_state = isSilenced ? SILENCED : SHOWN;
  138. [RemotingPreferences.instance setObject:UiStateToNSNumber(new_ui_state)
  139. forFlag:RemotingFlagNotificationUiState];
  140. [RemotingPreferences.instance setObject:@(notification_shown_times + 1)
  141. forFlag:RemotingFlagNotificationShownTimes];
  142. [RemotingPreferences.instance synchronizeFlags];
  143. }];
  144. }
  145. } // namespace remoting