From 4ccb8104922c174540ae4cb42c8e2b6557691243 Mon Sep 17 00:00:00 2001 From: cocoa-dev004 <66989461+cocoa-dev004@users.noreply.github.com> Date: Thu, 6 Oct 2022 09:15:12 +0900 Subject: [PATCH 1/5] Fixed end of service notification --- .../ExposureDetectionBackgroundService.cs | 6 +- .../ExposureDetectionBackgroundService.cs | 6 +- Covid19Radar/Covid19Radar/App.xaml.cs | 1 + .../Covid19Radar/Common/AppConstants.cs | 6 + .../Covid19Radar/Common/DateTimeUtility.cs | 2 + .../Covid19Radar/Common/PreferenceKey.cs | 3 + .../Repository/UserDataRepository.cs | 63 ++++ .../Resources/AppResources.Designer.cs | 2 +- .../Resources/AppResources.ja.resx | 4 +- .../Covid19Radar/Resources/AppResources.resx | 4 +- .../Resources/AppResources.zh-Hans.resx | 4 +- .../AbsExposureDetectionBackgroundService.cs | 14 +- .../EndOfServiceNotificationService.cs | 124 ++++++++ .../TerminationOfUsePageViewModel.cs | 5 +- .../ViewModels/Settings/DebugPageViewModel.cs | 15 +- .../Views/Settings/DebugPage.xaml | 8 + ...ExposureDetectionBackgroundServiceTests.cs | 11 +- .../EndOfServiceNotificationServiceTests.cs | 294 ++++++++++++++++++ .../TerminationOfUsePageViewModelTests.cs | 18 +- .../HomePage/HomePageViewModelTests.cs | 5 +- 20 files changed, 553 insertions(+), 42 deletions(-) create mode 100644 Covid19Radar/Covid19Radar/Services/EndOfServiceNotificationService.cs create mode 100644 Covid19Radar/Tests/Covid19Radar.UnitTests/Services/EndOfServiceNotificationServiceTests.cs diff --git a/Covid19Radar/Covid19Radar.Android/Services/ExposureDetectionBackgroundService.cs b/Covid19Radar/Covid19Radar.Android/Services/ExposureDetectionBackgroundService.cs index 19f652cfd..867f3cea4 100644 --- a/Covid19Radar/Covid19Radar.Android/Services/ExposureDetectionBackgroundService.cs +++ b/Covid19Radar/Covid19Radar.Android/Services/ExposureDetectionBackgroundService.cs @@ -38,7 +38,8 @@ public ExposureDetectionBackgroundService( IServerConfigurationRepository serverConfigurationRepository, ILocalPathService localPathService, IDateTimeUtility dateTimeUtility, - ILocalNotificationService localNotificationService + ILocalNotificationService localNotificationService, + IEndOfServiceNotificationService endOfServiceNotificationService ) : base( diagnosisKeyRepository, exposureNotificationApiService, @@ -48,7 +49,8 @@ ILocalNotificationService localNotificationService serverConfigurationRepository, localPathService, dateTimeUtility, - localNotificationService + localNotificationService, + endOfServiceNotificationService ) { _loggerService = loggerService; diff --git a/Covid19Radar/Covid19Radar.iOS/Services/ExposureDetectionBackgroundService.cs b/Covid19Radar/Covid19Radar.iOS/Services/ExposureDetectionBackgroundService.cs index cf69d7dc3..4b454b618 100644 --- a/Covid19Radar/Covid19Radar.iOS/Services/ExposureDetectionBackgroundService.cs +++ b/Covid19Radar/Covid19Radar.iOS/Services/ExposureDetectionBackgroundService.cs @@ -37,7 +37,8 @@ public ExposureDetectionBackgroundService( IServerConfigurationRepository serverConfigurationRepository, ILocalPathService localPathService, IDateTimeUtility dateTimeUtility, - ILocalNotificationService localNotificationService + ILocalNotificationService localNotificationService, + IEndOfServiceNotificationService endOfServiceNotificationService ) : base( diagnosisKeyRepository, exposureNotificationApiService, @@ -47,7 +48,8 @@ ILocalNotificationService localNotificationService serverConfigurationRepository, localPathService, dateTimeUtility, - localNotificationService + localNotificationService, + endOfServiceNotificationService ) { _loggerService = loggerService; diff --git a/Covid19Radar/Covid19Radar/App.xaml.cs b/Covid19Radar/Covid19Radar/App.xaml.cs index 29412ede5..ae085afdb 100644 --- a/Covid19Radar/Covid19Radar/App.xaml.cs +++ b/Covid19Radar/Covid19Radar/App.xaml.cs @@ -253,6 +253,7 @@ private static void RegisterCommonTypes(IContainer container) container.Register(Reuse.Singleton); // End of service + container.Register(Reuse.Singleton); container.Register(Reuse.Singleton); } diff --git a/Covid19Radar/Covid19Radar/Common/AppConstants.cs b/Covid19Radar/Covid19Radar/Common/AppConstants.cs index c3736c348..30013c592 100644 --- a/Covid19Radar/Covid19Radar/Common/AppConstants.cs +++ b/Covid19Radar/Covid19Radar/Common/AppConstants.cs @@ -15,6 +15,12 @@ public static class AppConstants public static readonly DateTime COCOA_FIRST_RELEASE_DATE = DateTime.SpecifyKind(new DateTime(2020, 06, 19, 9, 0, 0), DateTimeKind.Utc); + /// + /// Survey end date. (2023/01/01 00:00 JST 以降は調査期間外) + /// + public static readonly DateTime SURVEY_END_DATE_UTC + = new DateTimeOffset(2023, 1, 1, 0, 0, 0, new TimeSpan(9, 0, 0)).UtcDateTime; + /// /// Japan Standard Time (JST), UTC +9 /// diff --git a/Covid19Radar/Covid19Radar/Common/DateTimeUtility.cs b/Covid19Radar/Covid19Radar/Common/DateTimeUtility.cs index e0df58eaa..86b8678b4 100644 --- a/Covid19Radar/Covid19Radar/Common/DateTimeUtility.cs +++ b/Covid19Radar/Covid19Radar/Common/DateTimeUtility.cs @@ -9,10 +9,12 @@ namespace Covid19Radar.Common public interface IDateTimeUtility { public DateTime UtcNow { get; } + public DateTime JstNow { get; } } public class DateTimeUtility : IDateTimeUtility { public DateTime UtcNow => DateTime.UtcNow; + public DateTime JstNow => Utils.JstNow(); } } diff --git a/Covid19Radar/Covid19Radar/Common/PreferenceKey.cs b/Covid19Radar/Covid19Radar/Common/PreferenceKey.cs index 1303c291b..37aa15243 100644 --- a/Covid19Radar/Covid19Radar/Common/PreferenceKey.cs +++ b/Covid19Radar/Covid19Radar/Common/PreferenceKey.cs @@ -24,6 +24,9 @@ public static class PreferenceKey public const string DailySummaries = "DailySummaries"; public const string ExposureWindows = "ExposureWindows"; + public const string EndOfServiceNotificationNextSchedule = "EndOfServiceNotificationNextSchedule"; + public const string EndOfServiceNotificationCount = "EndOfServiceNotificationCount"; + // for ExposureConfigurationRepository public const string IsDiagnosisKeysDataMappingConfigurationUpdated = "IsDiagnosisKeysDataMappingConfigurationUpdated"; public const string ExposureConfigurationDownloadedEpoch = "ExposureConfigurationDownloadedEpoch"; diff --git a/Covid19Radar/Covid19Radar/Repository/UserDataRepository.cs b/Covid19Radar/Covid19Radar/Repository/UserDataRepository.cs index a34c3d0c8..a57365ea8 100644 --- a/Covid19Radar/Covid19Radar/Repository/UserDataRepository.cs +++ b/Covid19Radar/Covid19Radar/Repository/UserDataRepository.cs @@ -43,6 +43,18 @@ public interface IUserDataRepository Task GetLastProcessDiagnosisKeyTimestampAsync(string region); Task SetLastProcessDiagnosisKeyTimestampAsync(string region, long timestamp); Task RemoveLastProcessDiagnosisKeyTimestampAsync(); + + // for End of service + void SetEndOfServiceNotificationNextSchedule(DateTime nextScheduleDateTime); + DateTime? GetEndOfServiceNotificationNextSchedule(); + + void SetEndOfServiceNotificationCount(int count); + int GetEndOfServiceNotificationCount(); + + void RemoveAllOfEndOfServiceInformation(); + + // Remove all + void RemoveAll(); } public class UserDataRepository : IUserDataRepository @@ -261,5 +273,56 @@ public void RemoveAllExposureNotificationStatus() _preferencesService.RemoveValue(PreferenceKey.LastConfirmedDateTimeEpoch); _loggerService.EndMethod(); } + + public void SetEndOfServiceNotificationNextSchedule(DateTime nextSchedule) + { + _loggerService.StartMethod(); + _preferencesService.SetLongValue(PreferenceKey.EndOfServiceNotificationNextSchedule, nextSchedule.ToUnixEpoch()); + _loggerService.EndMethod(); + } + + public DateTime? GetEndOfServiceNotificationNextSchedule() + { + _loggerService.StartMethod(); + DateTime? nextSchedule = null; + try + { + if (_preferencesService.ContainsKey(PreferenceKey.EndOfServiceNotificationNextSchedule)) + { + long epoch = _preferencesService.GetLongValue(PreferenceKey.EndOfServiceNotificationNextSchedule, 0L); + nextSchedule = DateTime.UnixEpoch.AddSeconds(epoch); + } + } + finally + { + _loggerService.EndMethod(); + } + return nextSchedule; + } + + public void SetEndOfServiceNotificationCount(int count) + { + _preferencesService.SetIntValue(PreferenceKey.EndOfServiceNotificationCount, count); + } + + public int GetEndOfServiceNotificationCount() + { + return _preferencesService.GetIntValue(PreferenceKey.EndOfServiceNotificationCount, 0); ; + } + + public void RemoveAllOfEndOfServiceInformation() + { + _preferencesService.RemoveValue(PreferenceKey.EndOfServiceNotificationNextSchedule); + _preferencesService.RemoveValue(PreferenceKey.EndOfServiceNotificationCount); + } + + public void RemoveAll() + { + RemoveLastProcessDiagnosisKeyTimestampAsync(); + RemoveStartDate(); + RemoveAllUpdateDate(); + RemoveAllExposureNotificationStatus(); + RemoveAllOfEndOfServiceInformation(); + } } } diff --git a/Covid19Radar/Covid19Radar/Resources/AppResources.Designer.cs b/Covid19Radar/Covid19Radar/Resources/AppResources.Designer.cs index 803e9b31c..96dcc5aee 100644 --- a/Covid19Radar/Covid19Radar/Resources/AppResources.Designer.cs +++ b/Covid19Radar/Covid19Radar/Resources/AppResources.Designer.cs @@ -562,7 +562,7 @@ public static string EndOfServiceNoticePageTitle { } /// - /// Looks up a localized string similar to TODO. + /// Looks up a localized string similar to COCOA service is no longer available since ○ 2022. Please open the app to complete the termination procedure.. /// public static string EndOfServiceNotificationContent { get { diff --git a/Covid19Radar/Covid19Radar/Resources/AppResources.ja.resx b/Covid19Radar/Covid19Radar/Resources/AppResources.ja.resx index 42aa94177..1944a87f4 100644 --- a/Covid19Radar/Covid19Radar/Resources/AppResources.ja.resx +++ b/Covid19Radar/Covid19Radar/Resources/AppResources.ja.resx @@ -1012,8 +1012,8 @@ サービス終了のお知らせ - 接触確認アプリ「COCOA」は2022年◯月◯日をもってサービスを終了します。 - 接触確認アプリ「COCOA」は2022年◯月◯日をもってサービスを終了します。 + 2022年◯月をもってCOCOAは機能を停止しました。アプリを開いて機能停止手続きを行ってください。 + 2022年◯月をもってCOCOAは機能を停止しました。アプリを開いて機能停止手続きを行ってください。 ウェブアクセシビリティ方針 diff --git a/Covid19Radar/Covid19Radar/Resources/AppResources.resx b/Covid19Radar/Covid19Radar/Resources/AppResources.resx index b723b820d..a6cfe4018 100644 --- a/Covid19Radar/Covid19Radar/Resources/AppResources.resx +++ b/Covid19Radar/Covid19Radar/Resources/AppResources.resx @@ -1112,8 +1112,8 @@ You do not have to request it yourself. サービス終了のお知らせ - TODO - 接触確認アプリ「COCOA」は2022年◯月◯日をもってサービスを終了します。 + COCOA service is no longer available since ○ 2022. Please open the app to complete the termination procedure. + 2022年◯月をもってCOCOAは機能を停止しました。アプリを開いて機能停止手続きを行ってください。 Web accessibility policy diff --git a/Covid19Radar/Covid19Radar/Resources/AppResources.zh-Hans.resx b/Covid19Radar/Covid19Radar/Resources/AppResources.zh-Hans.resx index 09fb62d95..d42683f95 100644 --- a/Covid19Radar/Covid19Radar/Resources/AppResources.zh-Hans.resx +++ b/Covid19Radar/Covid19Radar/Resources/AppResources.zh-Hans.resx @@ -1015,8 +1015,8 @@ サービス終了のお知らせ - TODO - 接触確認アプリ「COCOA」は2022年◯月◯日をもってサービスを終了します。 + COCOA已于2022年○月停止运作。请打开应用来完成功能终止手续。 + 2022年◯月をもってCOCOAは機能を停止しました。アプリを開いて機能停止手続きを行ってください。 网页无障碍访问政策 diff --git a/Covid19Radar/Covid19Radar/Services/AbsExposureDetectionBackgroundService.cs b/Covid19Radar/Covid19Radar/Services/AbsExposureDetectionBackgroundService.cs index ba508e654..2068c0dbb 100644 --- a/Covid19Radar/Covid19Radar/Services/AbsExposureDetectionBackgroundService.cs +++ b/Covid19Radar/Covid19Radar/Services/AbsExposureDetectionBackgroundService.cs @@ -29,6 +29,7 @@ public abstract class AbsExposureDetectionBackgroundService : IBackgroundService private readonly ILocalPathService _localPathService; private readonly IDateTimeUtility _dateTimeUtility; private readonly ILocalNotificationService _localNotificationService; + private readonly IEndOfServiceNotificationService _endOfServiceNotificationService; private readonly SemaphoreSlim _semaphore = new SemaphoreSlim(1, 1); @@ -41,7 +42,8 @@ public AbsExposureDetectionBackgroundService( IServerConfigurationRepository serverConfigurationRepository, ILocalPathService localPathService, IDateTimeUtility dateTimeUtility, - ILocalNotificationService localNotificationService + ILocalNotificationService localNotificationService, + IEndOfServiceNotificationService endOfServiceNotificationService ) { _diagnosisKeyRepository = diagnosisKeyRepository; @@ -53,6 +55,7 @@ ILocalNotificationService localNotificationService _localPathService = localPathService; _dateTimeUtility = dateTimeUtility; _localNotificationService = localNotificationService; + _endOfServiceNotificationService = endOfServiceNotificationService; } public abstract void Schedule(); @@ -243,13 +246,6 @@ private bool CheckMaxPerDayExposureDetectionAPILimitReached(ENException ex) } public virtual async Task ShowEndOfServiceNotificationAync(CancellationTokenSource cancellationTokenSource = null) - { - _loggerService.StartMethod(); - if (_userDataRepository.IsAllAgreed()) - { - await _localNotificationService.ShowEndOfServiceNoticationAsync(); - } - _loggerService.EndMethod(); - } + => await _endOfServiceNotificationService.ShowNotificationAsync(cancellationTokenSource); } } diff --git a/Covid19Radar/Covid19Radar/Services/EndOfServiceNotificationService.cs b/Covid19Radar/Covid19Radar/Services/EndOfServiceNotificationService.cs new file mode 100644 index 000000000..81ba4c8d3 --- /dev/null +++ b/Covid19Radar/Covid19Radar/Services/EndOfServiceNotificationService.cs @@ -0,0 +1,124 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. +using System; +using System.Threading; +using System.Threading.Tasks; +using Covid19Radar.Common; +using Covid19Radar.Repository; +using Covid19Radar.Services.Logs; + +namespace Covid19Radar.Services +{ + public interface IEndOfServiceNotificationService + { + Task ShowNotificationAsync(CancellationTokenSource cancellationTokenSource = null); + } + + public class EndOfServiceNotificationService : IEndOfServiceNotificationService + { + private readonly ILoggerService _loggerService; + private readonly ILocalNotificationService _localNotificationService; + private readonly IDateTimeUtility _dateTimeUtility; + private readonly IUserDataRepository _userDataRepository; + + private readonly int MaxNotificationCount = 2; + private readonly int[] ScheduleDays = new int[] { 3, 4 }; + + public EndOfServiceNotificationService( + ILoggerService loggerService, + ILocalNotificationService localNotificationService, + IDateTimeUtility dateTimeUtility, + IUserDataRepository userDataRepository + ) + { + _loggerService = loggerService; + _localNotificationService = localNotificationService; + _dateTimeUtility = dateTimeUtility; + _userDataRepository = userDataRepository; + } + + public async Task ShowNotificationAsync(CancellationTokenSource cancellationTokenSource = null) + { + _loggerService.StartMethod(); + + try + { + DateTime utcNow = _dateTimeUtility.UtcNow; + DateTime jstNow = _dateTimeUtility.JstNow; + + if (!_userDataRepository.IsAllAgreed()) + { + _loggerService.Info("No notification (Is not agreed)"); + return; + } + + DateTime endDateUtc = AppConstants.SURVEY_END_DATE_UTC; + if (endDateUtc.CompareTo(utcNow) <= 0) + { + _loggerService.Info("No notification (Survey end)"); + return; + } + + int endOfServiceNotificationCount = _userDataRepository.GetEndOfServiceNotificationCount(); + if (endOfServiceNotificationCount > MaxNotificationCount - 1) + { + _loggerService.Info("No notification (Max notifications)"); + return; + } + + DateTime? nextSchedule = _userDataRepository.GetEndOfServiceNotificationNextSchedule(); + if (nextSchedule is null) + { + nextSchedule = GetNextSchedule(ScheduleDays[endOfServiceNotificationCount], utcNow); + _userDataRepository.SetEndOfServiceNotificationNextSchedule((DateTime)nextSchedule); + _loggerService.Info($"No notification (First schedule) {nextSchedule}"); + return; + } + + if (((DateTime)nextSchedule).CompareTo(utcNow) > 0) + { + _loggerService.Info("No notification (Before schedule)"); + return; + } + + if (jstNow.Hour < 9 || jstNow.Hour >= 21) + { + _loggerService.Info("No notification (Off hours)"); + return; + } + + endOfServiceNotificationCount++; + _userDataRepository.SetEndOfServiceNotificationCount(endOfServiceNotificationCount); + + if (endOfServiceNotificationCount <= MaxNotificationCount - 1) + { + nextSchedule = GetNextSchedule(ScheduleDays[endOfServiceNotificationCount], (DateTime)nextSchedule); + _userDataRepository.SetEndOfServiceNotificationNextSchedule((DateTime)nextSchedule); + _loggerService.Info($"Set next schedule. {nextSchedule}"); + } + + _loggerService.Info("Show notification."); + await _localNotificationService.ShowEndOfServiceNoticationAsync(); + } + finally + { + _loggerService.EndMethod(); + } + } + + private DateTime GetNextSchedule(int days, DateTime baseDateTimeUtc) + { + DateTime baseDateTimeJst = TimeZoneInfo.ConvertTimeFromUtc(baseDateTimeUtc, AppConstants.TIMEZONE_JST); + DateTime nextScheduleDate = baseDateTimeJst.Date.AddDays(days); + + var random = new Random(); + int seconds = random.Next(9 * 60 * 60, 21 * 60 * 60 - 1); + + DateTime nexeScheculeDateTime = nextScheduleDate.AddSeconds(seconds); + + return TimeZoneInfo.ConvertTimeToUtc(nexeScheculeDateTime, AppConstants.TIMEZONE_JST); + } + } +} + diff --git a/Covid19Radar/Covid19Radar/ViewModels/EndOfService/TerminationOfUsePageViewModel.cs b/Covid19Radar/Covid19Radar/ViewModels/EndOfService/TerminationOfUsePageViewModel.cs index 61def9a66..93518a05b 100644 --- a/Covid19Radar/Covid19Radar/ViewModels/EndOfService/TerminationOfUsePageViewModel.cs +++ b/Covid19Radar/Covid19Radar/ViewModels/EndOfService/TerminationOfUsePageViewModel.cs @@ -102,12 +102,9 @@ public override void Initialize(INavigationParameters parameters) await _exposureDataRepository.RemoveDailySummariesAsync(); await _exposureDataRepository.RemoveExposureWindowsAsync(); _exposureDataRepository.RemoveExposureInformation(); - await _userDataRepository.RemoveLastProcessDiagnosisKeyTimestampAsync(); await _exposureConfigurationRepository.RemoveExposureConfigurationAsync(); - _userDataRepository.RemoveStartDate(); - _userDataRepository.RemoveAllUpdateDate(); - _userDataRepository.RemoveAllExposureNotificationStatus(); + _userDataRepository.RemoveAll(); _sendEventLogStateRepository.RemoveAll(); await _eventLogRepository.RemoveAllAsync(); diff --git a/Covid19Radar/Covid19Radar/ViewModels/Settings/DebugPageViewModel.cs b/Covid19Radar/Covid19Radar/ViewModels/Settings/DebugPageViewModel.cs index a516d9b98..167cc79d5 100644 --- a/Covid19Radar/Covid19Radar/ViewModels/Settings/DebugPageViewModel.cs +++ b/Covid19Radar/Covid19Radar/ViewModels/Settings/DebugPageViewModel.cs @@ -38,6 +38,7 @@ public class DebugPageViewModel : ViewModelBase private readonly ISendEventLogStateRepository _sendEventLogStateRepository; private readonly IEventLogRepository _eventLogRepository; private readonly IEventLogService _eventLogService; + private readonly IEndOfServiceNotificationService _endOfServiceNotificationService; private string _debugInfo; public string DebugInfo @@ -169,7 +170,8 @@ public DebugPageViewModel( ILocalNotificationService localNotificationService, ISendEventLogStateRepository sendEventLogStateRepository, IEventLogRepository eventLogRepository, - IEventLogService eventLogService + IEventLogService eventLogService, + IEndOfServiceNotificationService endOfServiceNotificationService ) : base(navigationService) { Title = "Title:Debug"; @@ -185,6 +187,7 @@ IEventLogService eventLogService _sendEventLogStateRepository = sendEventLogStateRepository; _eventLogRepository = eventLogRepository; _eventLogService = eventLogService; + _endOfServiceNotificationService = endOfServiceNotificationService; } public override async void Initialize(INavigationParameters parameters) @@ -239,6 +242,16 @@ public override async void Initialize(INavigationParameters parameters) await _localNotificationService.ShowEndOfServiceNoticationAsync(); }); + public Command OnClickShowEndOfServiceNotification2 => new Command(async () => + { + await _endOfServiceNotificationService.ShowNotificationAsync(); + }); + + public Command OnClickRemoveEndOfServiceInformation => new Command(async () => + { + _userDataRepository.RemoveAllOfEndOfServiceInformation(); + }); + public Command OnClickExportExposureWindow => new Command(async () => { var exposureWindows = await _exposureDataRepository.GetExposureWindowsAsync(); diff --git a/Covid19Radar/Covid19Radar/Views/Settings/DebugPage.xaml b/Covid19Radar/Covid19Radar/Views/Settings/DebugPage.xaml index f3610faf7..ea6bfe9a2 100644 --- a/Covid19Radar/Covid19Radar/Views/Settings/DebugPage.xaml +++ b/Covid19Radar/Covid19Radar/Views/Settings/DebugPage.xaml @@ -79,7 +79,15 @@