|
@@ -9,6 +9,7 @@ import 'package:get/get.dart'; |
|
@@ -9,6 +9,7 @@ import 'package:get/get.dart'; |
|
9
|
|
9
|
|
|
10
|
import '../../../../data/models/enums/app_enums.dart';
|
10
|
import '../../../../data/models/enums/app_enums.dart';
|
|
11
|
import '../../../../data/models/health/health_v2_models.dart';
|
11
|
import '../../../../data/models/health/health_v2_models.dart';
|
|
|
|
12
|
+import '../../../../l10n/l10n_extensions.dart';
|
|
12
|
import '../../../../pigeon/health_kit_raw_data_api.g.dart';
|
13
|
import '../../../../pigeon/health_kit_raw_data_api.g.dart';
|
|
13
|
import '../../../config/app_environment_config.dart';
|
14
|
import '../../../config/app_environment_config.dart';
|
|
14
|
import '../../../logging/app_logger.dart';
|
15
|
import '../../../logging/app_logger.dart';
|
|
@@ -19,6 +20,7 @@ import '../health_raw_data_source.dart'; |
|
@@ -19,6 +20,7 @@ import '../health_raw_data_source.dart'; |
|
19
|
import '../health_raw_models.dart';
|
20
|
import '../health_raw_models.dart';
|
|
20
|
import '../health_sleep_calculator.dart';
|
21
|
import '../health_sleep_calculator.dart';
|
|
21
|
import '../platform_ios/apple_health_raw_data_core_service.dart';
|
22
|
import '../platform_ios/apple_health_raw_data_core_service.dart';
|
|
|
|
23
|
+import '../platform_ios/apple_health_raw_local_notification.dart';
|
|
22
|
import 'huawei_health_raw_stress_calculator.dart';
|
24
|
import 'huawei_health_raw_stress_calculator.dart';
|
|
23
|
import 'ohos_health_raw_data_sync_service.dart';
|
25
|
import 'ohos_health_raw_data_sync_service.dart';
|
|
24
|
import 'ohos_health_raw_result_upload_service.dart';
|
26
|
import 'ohos_health_raw_result_upload_service.dart';
|
|
@@ -26,6 +28,9 @@ import 'ohos_health_raw_result_upload_service.dart'; |
|
@@ -26,6 +28,9 @@ import 'ohos_health_raw_result_upload_service.dart'; |
|
26
|
class OHOSHealthRawDataCoreService {
|
28
|
class OHOSHealthRawDataCoreService {
|
|
27
|
static const int defaultLookbackDays = 183;
|
29
|
static const int defaultLookbackDays = 183;
|
|
28
|
static const int defaultReadChunkDays = 7;
|
30
|
static const int defaultReadChunkDays = 7;
|
|
|
|
31
|
+ static const String _calculateLogMarker = '[OHOS_HEALTH_CALCULATE]';
|
|
|
|
32
|
+ static const String _dailyStressLogMarker = '[OHOS_DAILY_STRESS_CALC]';
|
|
|
|
33
|
+ static const String _sleepCalcLogMarker = '[OHOS_SLEEP_CALC]';
|
|
29
|
|
34
|
|
|
30
|
OHOSHealthRawDataCoreService({
|
35
|
OHOSHealthRawDataCoreService({
|
|
31
|
HealthRawDataSource? rawDataSource,
|
36
|
HealthRawDataSource? rawDataSource,
|
|
@@ -37,6 +42,7 @@ class OHOSHealthRawDataCoreService { |
|
@@ -37,6 +42,7 @@ class OHOSHealthRawDataCoreService { |
|
37
|
HarmonyApi? harmonyApi,
|
42
|
HarmonyApi? harmonyApi,
|
|
38
|
Future<bool> Function()? healthReadAuthorizationChecker,
|
43
|
Future<bool> Function()? healthReadAuthorizationChecker,
|
|
39
|
OhosHealthRawResultUploadService? resultUploadService,
|
44
|
OhosHealthRawResultUploadService? resultUploadService,
|
|
|
|
45
|
+ HealthRawLocalNotificationDispatcher? localNotificationDispatcher,
|
|
40
|
}) : _rawDataSource = rawDataSource ??
|
46
|
}) : _rawDataSource = rawDataSource ??
|
|
41
|
OhosHealthRawDataSource(
|
47
|
OhosHealthRawDataSource(
|
|
42
|
userIdProvider: userIdProvider,
|
48
|
userIdProvider: userIdProvider,
|
|
@@ -51,7 +57,9 @@ class OHOSHealthRawDataCoreService { |
|
@@ -51,7 +57,9 @@ class OHOSHealthRawDataCoreService { |
|
51
|
_harmonyApi = harmonyApi ??
|
57
|
_harmonyApi = harmonyApi ??
|
|
52
|
(Get.isRegistered<HarmonyApi>() ? Get.find<HarmonyApi>() : null),
|
58
|
(Get.isRegistered<HarmonyApi>() ? Get.find<HarmonyApi>() : null),
|
|
53
|
_healthReadAuthorizationChecker = healthReadAuthorizationChecker,
|
59
|
_healthReadAuthorizationChecker = healthReadAuthorizationChecker,
|
|
54
|
- _resultUploadService = resultUploadService;
|
60
|
+ _resultUploadService = resultUploadService,
|
|
|
|
61
|
+ _localNotificationDispatcher = localNotificationDispatcher ??
|
|
|
|
62
|
+ HealthRawLocalNotificationDispatcher();
|
|
55
|
|
63
|
|
|
56
|
final HealthRawDataSource _rawDataSource;
|
64
|
final HealthRawDataSource _rawDataSource;
|
|
57
|
final HealthRawStressLocalStore _localStore;
|
65
|
final HealthRawStressLocalStore _localStore;
|
|
@@ -62,6 +70,7 @@ class OHOSHealthRawDataCoreService { |
|
@@ -62,6 +70,7 @@ class OHOSHealthRawDataCoreService { |
|
62
|
final HarmonyApi? _harmonyApi;
|
70
|
final HarmonyApi? _harmonyApi;
|
|
63
|
final Future<bool> Function()? _healthReadAuthorizationChecker;
|
71
|
final Future<bool> Function()? _healthReadAuthorizationChecker;
|
|
64
|
OhosHealthRawResultUploadService? _resultUploadService;
|
72
|
OhosHealthRawResultUploadService? _resultUploadService;
|
|
|
|
73
|
+ final HealthRawLocalNotificationDispatcher _localNotificationDispatcher;
|
|
65
|
final StreamController<HealthRawDataUpdatedEvent>
|
74
|
final StreamController<HealthRawDataUpdatedEvent>
|
|
66
|
_healthDataUpdatedController =
|
75
|
_healthDataUpdatedController =
|
|
67
|
StreamController<HealthRawDataUpdatedEvent>.broadcast();
|
76
|
StreamController<HealthRawDataUpdatedEvent>.broadcast();
|
|
@@ -222,159 +231,214 @@ class OHOSHealthRawDataCoreService { |
|
@@ -222,159 +231,214 @@ class OHOSHealthRawDataCoreService { |
|
222
|
if (effectiveEndTime < requestedStartTime) {
|
231
|
if (effectiveEndTime < requestedStartTime) {
|
|
223
|
throw ArgumentError.value(endTime, 'endTime');
|
232
|
throw ArgumentError.value(endTime, 'endTime');
|
|
224
|
}
|
233
|
}
|
|
225
|
- final hasAuthorization = await _hasHealthReadAuthorization();
|
|
|
|
226
|
- final syncResults = hasAuthorization
|
|
|
|
227
|
- ? await _syncCalculationRawDataIfNeeded(
|
|
|
|
228
|
- startTime: requestedStartTime,
|
|
|
|
229
|
- endTime: effectiveEndTime,
|
|
|
|
230
|
- )
|
|
|
|
231
|
- : const <OhosHealthRawDataSyncResult>[];
|
|
|
|
232
|
- if (!hasAuthorization) {
|
|
|
|
233
|
- _logInfo(
|
|
|
|
234
|
- 'skip OHOS raw data sync without health privacy authorization; '
|
|
|
|
235
|
- 'continue calculate and upload from local database',
|
|
|
|
236
|
- );
|
|
|
|
237
|
- }
|
234
|
+ final hasAuthorization = await _hasHealthReadAuthorizationSafely();
|
|
|
|
235
|
+ final syncResults = await _syncCalculationRawDataSafely(
|
|
|
|
236
|
+ hasAuthorization: hasAuthorization,
|
|
|
|
237
|
+ startTime: requestedStartTime,
|
|
|
|
238
|
+ endTime: effectiveEndTime,
|
|
|
|
239
|
+ );
|
|
238
|
_logInfo(
|
240
|
_logInfo(
|
|
239
|
'calculate_sync_finish startTime=$requestedStartTime '
|
241
|
'calculate_sync_finish startTime=$requestedStartTime '
|
|
240
|
'endTime=$effectiveEndTime syncResults=$syncResults',
|
242
|
'endTime=$effectiveEndTime syncResults=$syncResults',
|
|
241
|
);
|
243
|
);
|
|
242
|
|
244
|
|
|
243
|
- final hrvContextStart = await _localStore.latestHrvSourceStartTime(userId);
|
|
|
|
244
|
- final realtimeContextStart =
|
|
|
|
245
|
- await _localStore.latestRealtimeSourceStartTime(userId);
|
|
|
|
246
|
- final latestHrvRawEndTime = await _localStore.latestHrvRawEndTime(userId);
|
|
|
|
247
|
- final latestRealtimeRawEndTime =
|
|
|
|
248
|
- await _localStore.latestRealtimeRawEndTime(userId);
|
|
|
|
249
|
- final latestSleepResultTime = await _localStore.latestSleepResultTime(
|
|
|
|
250
|
- userId,
|
|
|
|
251
|
- );
|
|
|
|
252
|
- final hrvStartTime = math.max(
|
|
|
|
253
|
- hrvContextStart ?? requestedStartTime,
|
|
|
|
254
|
- earliestStartTime,
|
|
|
|
255
|
- );
|
|
|
|
256
|
- final realtimeStartTime = math.max(
|
|
|
|
257
|
- realtimeContextStart ?? requestedStartTime,
|
|
|
|
258
|
- earliestStartTime,
|
|
|
|
259
|
- );
|
|
|
|
260
|
- final heartRateStartTime = math.max(
|
|
|
|
261
|
- _minNullable(hrvStartTime, realtimeStartTime) ?? requestedStartTime,
|
|
|
|
262
|
- earliestStartTime,
|
|
|
|
263
|
- );
|
|
|
|
264
|
- final sleepStartTime = math.max(
|
|
|
|
265
|
- latestSleepResultTime == null
|
|
|
|
266
|
- ? requestedStartTime
|
|
|
|
267
|
- : latestSleepResultTime - Duration.secondsPerDay,
|
|
|
|
268
|
- earliestStartTime,
|
|
|
|
269
|
- );
|
|
|
|
270
|
- _logInfo(
|
|
|
|
271
|
- 'calculate_context userId=$userId '
|
|
|
|
272
|
- 'requestedStartTime=$requestedStartTime endTime=$effectiveEndTime '
|
|
|
|
273
|
- 'hrvContextStart=$hrvContextStart '
|
|
|
|
274
|
- 'realtimeContextStart=$realtimeContextStart '
|
|
|
|
275
|
- 'latestHrvRawEndTime=$latestHrvRawEndTime '
|
|
|
|
276
|
- 'latestRealtimeRawEndTime=$latestRealtimeRawEndTime '
|
|
|
|
277
|
- 'latestSleepResultTime=$latestSleepResultTime '
|
|
|
|
278
|
- 'hrvStartTime=$hrvStartTime heartRateStartTime=$heartRateStartTime '
|
|
|
|
279
|
- 'sleepStartTime=$sleepStartTime',
|
|
|
|
280
|
- );
|
|
|
|
281
|
-
|
|
|
|
282
|
- final hrvPoints = await _fetchRawDataInChunks(
|
|
|
|
283
|
- HealthDataUploadType.hrv.type,
|
|
|
|
284
|
- hrvStartTime,
|
|
|
|
285
|
- effectiveEndTime,
|
|
|
|
286
|
- readChunkDays: readChunkDays,
|
|
|
|
287
|
- );
|
|
|
|
288
|
- final heartRatePoints = await _fetchRawDataInChunks(
|
|
|
|
289
|
- HealthDataUploadType.heartRate.type,
|
|
|
|
290
|
- heartRateStartTime,
|
|
|
|
291
|
- effectiveEndTime,
|
|
|
|
292
|
- readChunkDays: readChunkDays,
|
|
|
|
293
|
- );
|
|
|
|
294
|
- final restingHeartRatePoints = await _fetchRawDataInChunks(
|
|
|
|
295
|
- HealthDataUploadType.restingHeartRate.type,
|
|
|
|
296
|
- heartRateStartTime,
|
|
|
|
297
|
- effectiveEndTime,
|
|
|
|
298
|
- readChunkDays: readChunkDays,
|
|
|
|
299
|
- );
|
|
|
|
300
|
- final sleepIntervals = await _fetchSleepIntervalsInChunks(
|
|
|
|
301
|
- sleepStartTime,
|
|
|
|
302
|
- effectiveEndTime,
|
|
|
|
303
|
- readChunkDays: readChunkDays,
|
|
|
|
304
|
- );
|
|
|
|
305
|
- final workoutIntervals = await _fetchWorkoutIntervalsInChunks(
|
|
|
|
306
|
- heartRateStartTime,
|
|
|
|
307
|
- effectiveEndTime,
|
245
|
+ final storedResult = await _calculateAndStoreSafely(
|
|
|
|
246
|
+ userId: userId,
|
|
|
|
247
|
+ requestedStartTime: requestedStartTime,
|
|
|
|
248
|
+ effectiveEndTime: effectiveEndTime,
|
|
|
|
249
|
+ earliestStartTime: earliestStartTime,
|
|
308
|
readChunkDays: readChunkDays,
|
250
|
readChunkDays: readChunkDays,
|
|
309
|
);
|
251
|
);
|
|
310
|
- _logInfo(
|
|
|
|
311
|
- 'calculate_raw_counts userId=$userId '
|
|
|
|
312
|
- 'hrv=${hrvPoints.length} heartRate=${heartRatePoints.length} '
|
|
|
|
313
|
- 'restingHeartRate=${restingHeartRatePoints.length} '
|
|
|
|
314
|
- 'sleepIntervals=${sleepIntervals.length} '
|
|
|
|
315
|
- 'workoutIntervals=${workoutIntervals.length}',
|
|
|
|
316
|
- );
|
|
|
|
317
|
-
|
|
|
|
318
|
- final result = await Isolate.run(
|
|
|
|
319
|
- () => HuaweiHealthRawStressCalculator(userId: userId).calculate(
|
|
|
|
320
|
- hrvPoints: hrvPoints,
|
|
|
|
321
|
- heartRatePoints: heartRatePoints,
|
|
|
|
322
|
- restingHeartRatePoints: restingHeartRatePoints,
|
|
|
|
323
|
- sleepIntervals: sleepIntervals,
|
|
|
|
324
|
- workoutIntervals: workoutIntervals,
|
|
|
|
325
|
- startTime: math.min(hrvStartTime, heartRateStartTime),
|
|
|
|
326
|
- endTime: effectiveEndTime,
|
|
|
|
327
|
- ),
|
|
|
|
328
|
- debugName: 'OHOSHealthRawStressCalculator',
|
|
|
|
329
|
- );
|
|
|
|
330
|
- _logInfo(
|
|
|
|
331
|
- 'calculate_result_counts userId=$userId '
|
|
|
|
332
|
- 'hrv=${result.hrvStressPoints.length} '
|
|
|
|
333
|
- 'realtime=${result.realtimeStressPoints.length}',
|
|
|
|
334
|
- );
|
|
|
|
335
|
- final newResult = result.copyWith(
|
|
|
|
336
|
- hrvStressPoints: _filterNewHrvStressPoints(
|
|
|
|
337
|
- result.hrvStressPoints,
|
|
|
|
338
|
- latestHrvRawEndTime,
|
|
|
|
339
|
- ),
|
|
|
|
340
|
- realtimeStressPoints: _filterNewRealtimeStressPoints(
|
|
|
|
341
|
- result.realtimeStressPoints,
|
|
|
|
342
|
- latestRealtimeRawEndTime,
|
|
|
|
343
|
- ),
|
|
|
|
344
|
- );
|
|
|
|
345
|
- _logInfo(
|
|
|
|
346
|
- 'calculate_new_result_counts userId=$userId '
|
|
|
|
347
|
- 'hrv=${newResult.hrvStressPoints.length} '
|
|
|
|
348
|
- 'realtime=${newResult.realtimeStressPoints.length}',
|
|
|
|
349
|
- );
|
|
|
|
350
|
- await _localStore.upsertResult(newResult);
|
|
|
|
351
|
- _logInfo(
|
|
|
|
352
|
- 'calculate_result_stored userId=$userId '
|
|
|
|
353
|
- 'hrv=${newResult.hrvStressPoints.length} '
|
|
|
|
354
|
- 'realtime=${newResult.realtimeStressPoints.length}',
|
252
|
+ _scheduleResultUpload();
|
|
|
|
253
|
+ await _sendLocalNotificationsAfterCalculationSafely(
|
|
|
|
254
|
+ result: storedResult.result,
|
|
|
|
255
|
+ hasExistingHrv: storedResult.hasExistingHrv,
|
|
|
|
256
|
+ hasExistingSleep: storedResult.hasExistingSleep,
|
|
355
|
);
|
257
|
);
|
|
|
|
258
|
+ return storedResult.result;
|
|
|
|
259
|
+ }
|
|
356
|
|
260
|
|
|
357
|
- final dailyStressPoints = await _calculateAndStoreDailyStressPoints(
|
|
|
|
358
|
- userId: userId,
|
|
|
|
359
|
- realtimePoints: newResult.realtimeStressPoints,
|
|
|
|
360
|
- nowSeconds: effectiveEndTime,
|
|
|
|
361
|
- );
|
|
|
|
362
|
- final sleepResults = await _calculateAndStoreSleepResults(
|
261
|
+ Future<_OhosStoredCalculationResult> _calculateAndStoreSafely({
|
|
|
|
262
|
+ required int userId,
|
|
|
|
263
|
+ required int requestedStartTime,
|
|
|
|
264
|
+ required int effectiveEndTime,
|
|
|
|
265
|
+ required int earliestStartTime,
|
|
|
|
266
|
+ required int readChunkDays,
|
|
|
|
267
|
+ }) async {
|
|
|
|
268
|
+ var hasExistingHrv = false;
|
|
|
|
269
|
+ var hasExistingSleep = false;
|
|
|
|
270
|
+ final emptyResult = HealthRawStressCalculationResult(
|
|
363
|
userId: userId,
|
271
|
userId: userId,
|
|
364
|
- sleepIntervals: sleepIntervals,
|
|
|
|
365
|
- latestSleepResultTime: latestSleepResultTime,
|
272
|
+ hrvStressPoints: const <HealthRawHrvStressPoint>[],
|
|
|
|
273
|
+ realtimeStressPoints: const <HealthRawRealtimeStressPoint>[],
|
|
|
|
274
|
+ dailyStressPoints: const <HealthRawDailyStressPoint>[],
|
|
|
|
275
|
+ sleepResults: const <HealthRawSleepResult>[],
|
|
366
|
);
|
276
|
);
|
|
367
|
- _logInfo(
|
|
|
|
368
|
- 'calculate_daily_sleep_stored userId=$userId '
|
|
|
|
369
|
- 'daily=${dailyStressPoints.length} sleep=${sleepResults.length}',
|
|
|
|
370
|
- );
|
|
|
|
371
|
- if (_uploadResultsAfterCalculation) {
|
|
|
|
372
|
- await _uploadResults();
|
277
|
+ try {
|
|
|
|
278
|
+ final hrvContextStart =
|
|
|
|
279
|
+ await _localStore.latestHrvSourceStartTime(userId);
|
|
|
|
280
|
+ final realtimeContextStart =
|
|
|
|
281
|
+ await _localStore.latestRealtimeSourceStartTime(userId);
|
|
|
|
282
|
+ final latestHrvRawEndTime = await _localStore.latestHrvRawEndTime(userId);
|
|
|
|
283
|
+ final latestRealtimeRawEndTime =
|
|
|
|
284
|
+ await _localStore.latestRealtimeRawEndTime(userId);
|
|
|
|
285
|
+ final latestSleepResultTime = await _localStore.latestSleepResultTime(
|
|
|
|
286
|
+ userId,
|
|
|
|
287
|
+ );
|
|
|
|
288
|
+ hasExistingHrv = latestHrvRawEndTime != null;
|
|
|
|
289
|
+ hasExistingSleep = latestSleepResultTime != null;
|
|
|
|
290
|
+
|
|
|
|
291
|
+ final hrvStartTime = math.max(
|
|
|
|
292
|
+ hrvContextStart ?? requestedStartTime,
|
|
|
|
293
|
+ earliestStartTime,
|
|
|
|
294
|
+ );
|
|
|
|
295
|
+ final realtimeStartTime = math.max(
|
|
|
|
296
|
+ realtimeContextStart ?? requestedStartTime,
|
|
|
|
297
|
+ earliestStartTime,
|
|
|
|
298
|
+ );
|
|
|
|
299
|
+ final heartRateStartTime = math.max(
|
|
|
|
300
|
+ _minNullable(hrvStartTime, realtimeStartTime) ?? requestedStartTime,
|
|
|
|
301
|
+ earliestStartTime,
|
|
|
|
302
|
+ );
|
|
|
|
303
|
+ final sleepStartTime = math.max(
|
|
|
|
304
|
+ latestSleepResultTime == null
|
|
|
|
305
|
+ ? requestedStartTime
|
|
|
|
306
|
+ : latestSleepResultTime - Duration.secondsPerDay,
|
|
|
|
307
|
+ earliestStartTime,
|
|
|
|
308
|
+ );
|
|
|
|
309
|
+ _logInfo(
|
|
|
|
310
|
+ 'calculate_context userId=$userId '
|
|
|
|
311
|
+ 'requestedStartTime=$requestedStartTime endTime=$effectiveEndTime '
|
|
|
|
312
|
+ 'hrvContextStart=$hrvContextStart '
|
|
|
|
313
|
+ 'realtimeContextStart=$realtimeContextStart '
|
|
|
|
314
|
+ 'latestHrvRawEndTime=$latestHrvRawEndTime '
|
|
|
|
315
|
+ 'latestRealtimeRawEndTime=$latestRealtimeRawEndTime '
|
|
|
|
316
|
+ 'latestSleepResultTime=$latestSleepResultTime '
|
|
|
|
317
|
+ 'hrvStartTime=$hrvStartTime heartRateStartTime=$heartRateStartTime '
|
|
|
|
318
|
+ 'sleepStartTime=$sleepStartTime',
|
|
|
|
319
|
+ );
|
|
|
|
320
|
+
|
|
|
|
321
|
+ final hrvPoints = await _fetchRawDataInChunks(
|
|
|
|
322
|
+ HealthDataUploadType.hrv.type,
|
|
|
|
323
|
+ hrvStartTime,
|
|
|
|
324
|
+ effectiveEndTime,
|
|
|
|
325
|
+ readChunkDays: readChunkDays,
|
|
|
|
326
|
+ );
|
|
|
|
327
|
+ final heartRatePoints = await _fetchRawDataInChunks(
|
|
|
|
328
|
+ HealthDataUploadType.heartRate.type,
|
|
|
|
329
|
+ heartRateStartTime,
|
|
|
|
330
|
+ effectiveEndTime,
|
|
|
|
331
|
+ readChunkDays: readChunkDays,
|
|
|
|
332
|
+ );
|
|
|
|
333
|
+ final restingHeartRatePoints = await _fetchRawDataInChunks(
|
|
|
|
334
|
+ HealthDataUploadType.restingHeartRate.type,
|
|
|
|
335
|
+ heartRateStartTime,
|
|
|
|
336
|
+ effectiveEndTime,
|
|
|
|
337
|
+ readChunkDays: readChunkDays,
|
|
|
|
338
|
+ );
|
|
|
|
339
|
+ final sleepIntervals = await _fetchSleepIntervalsInChunks(
|
|
|
|
340
|
+ sleepStartTime,
|
|
|
|
341
|
+ effectiveEndTime,
|
|
|
|
342
|
+ readChunkDays: readChunkDays,
|
|
|
|
343
|
+ );
|
|
|
|
344
|
+ final workoutIntervals = await _fetchWorkoutIntervalsInChunks(
|
|
|
|
345
|
+ heartRateStartTime,
|
|
|
|
346
|
+ effectiveEndTime,
|
|
|
|
347
|
+ readChunkDays: readChunkDays,
|
|
|
|
348
|
+ );
|
|
|
|
349
|
+ final latestRawHrvTime = _latestRawDataTime(hrvPoints);
|
|
|
|
350
|
+ final latestRawHrTime = _latestRawDataTime(heartRatePoints);
|
|
|
|
351
|
+ final latestRawSleepTime = _latestRawDataTime(sleepIntervals);
|
|
|
|
352
|
+ _logInfo(
|
|
|
|
353
|
+ '$_calculateLogMarker raw_counts userId=$userId '
|
|
|
|
354
|
+ 'hrv=${hrvPoints.length} heartRate=${heartRatePoints.length} '
|
|
|
|
355
|
+ 'restingHeartRate=${restingHeartRatePoints.length} '
|
|
|
|
356
|
+ 'sleepIntervals=${sleepIntervals.length} '
|
|
|
|
357
|
+ 'workoutIntervals=${workoutIntervals.length} '
|
|
|
|
358
|
+ 'latestRawHrvTime=$latestRawHrvTime '
|
|
|
|
359
|
+ 'latestRawHrTime=$latestRawHrTime '
|
|
|
|
360
|
+ 'latestRawSleepTime=$latestRawSleepTime '
|
|
|
|
361
|
+ 'needHrv=${_needsNewResult(latestRawHrvTime, latestHrvRawEndTime)} '
|
|
|
|
362
|
+ 'needRealtime=${_needsNewResult(latestRawHrTime, latestRealtimeRawEndTime)} '
|
|
|
|
363
|
+ 'needSleep=${_needsNewResult(latestRawSleepTime, latestSleepResultTime)}',
|
|
|
|
364
|
+ );
|
|
|
|
365
|
+
|
|
|
|
366
|
+ final result = await Isolate.run(
|
|
|
|
367
|
+ () => HuaweiHealthRawStressCalculator(userId: userId).calculate(
|
|
|
|
368
|
+ hrvPoints: hrvPoints,
|
|
|
|
369
|
+ heartRatePoints: heartRatePoints,
|
|
|
|
370
|
+ restingHeartRatePoints: restingHeartRatePoints,
|
|
|
|
371
|
+ sleepIntervals: sleepIntervals,
|
|
|
|
372
|
+ workoutIntervals: workoutIntervals,
|
|
|
|
373
|
+ startTime: math.min(hrvStartTime, heartRateStartTime),
|
|
|
|
374
|
+ endTime: effectiveEndTime,
|
|
|
|
375
|
+ ),
|
|
|
|
376
|
+ debugName: 'OHOSHealthRawStressCalculator',
|
|
|
|
377
|
+ );
|
|
|
|
378
|
+ _logInfo(
|
|
|
|
379
|
+ '$_calculateLogMarker result_counts userId=$userId '
|
|
|
|
380
|
+ 'hrv=${result.hrvStressPoints.length} '
|
|
|
|
381
|
+ 'realtime=${result.realtimeStressPoints.length} '
|
|
|
|
382
|
+ 'daily=${result.dailyStressPoints.length}',
|
|
|
|
383
|
+ );
|
|
|
|
384
|
+ final newResult = result.copyWith(
|
|
|
|
385
|
+ hrvStressPoints: _filterNewHrvStressPoints(
|
|
|
|
386
|
+ result.hrvStressPoints,
|
|
|
|
387
|
+ latestHrvRawEndTime,
|
|
|
|
388
|
+ ),
|
|
|
|
389
|
+ realtimeStressPoints: _filterNewRealtimeStressPoints(
|
|
|
|
390
|
+ result.realtimeStressPoints,
|
|
|
|
391
|
+ latestRealtimeRawEndTime,
|
|
|
|
392
|
+ ),
|
|
|
|
393
|
+ );
|
|
|
|
394
|
+ _logInfo(
|
|
|
|
395
|
+ '$_calculateLogMarker new_result_counts userId=$userId '
|
|
|
|
396
|
+ 'latestHrvRawEndTime=$latestHrvRawEndTime '
|
|
|
|
397
|
+ 'latestRealtimeRawEndTime=$latestRealtimeRawEndTime '
|
|
|
|
398
|
+ 'hrv=${newResult.hrvStressPoints.length} '
|
|
|
|
399
|
+ 'realtime=${newResult.realtimeStressPoints.length}',
|
|
|
|
400
|
+ );
|
|
|
|
401
|
+ await _localStore.upsertResult(newResult);
|
|
|
|
402
|
+ _logInfo(
|
|
|
|
403
|
+ '$_calculateLogMarker result_stored userId=$userId '
|
|
|
|
404
|
+ 'hrv=${newResult.hrvStressPoints.length} '
|
|
|
|
405
|
+ 'realtime=${newResult.realtimeStressPoints.length}',
|
|
|
|
406
|
+ );
|
|
|
|
407
|
+
|
|
|
|
408
|
+ final dailyStressPoints = await _calculateAndStoreDailyStressPoints(
|
|
|
|
409
|
+ userId: userId,
|
|
|
|
410
|
+ realtimePoints: newResult.realtimeStressPoints,
|
|
|
|
411
|
+ nowSeconds: effectiveEndTime,
|
|
|
|
412
|
+ );
|
|
|
|
413
|
+ final sleepResults = await _calculateAndStoreSleepResults(
|
|
|
|
414
|
+ userId: userId,
|
|
|
|
415
|
+ sleepIntervals: sleepIntervals,
|
|
|
|
416
|
+ latestSleepResultTime: latestSleepResultTime,
|
|
|
|
417
|
+ );
|
|
|
|
418
|
+ _logInfo(
|
|
|
|
419
|
+ 'calculate_daily_sleep_stored userId=$userId '
|
|
|
|
420
|
+ 'daily=${dailyStressPoints.length} sleep=${sleepResults.length}',
|
|
|
|
421
|
+ );
|
|
|
|
422
|
+ return _OhosStoredCalculationResult(
|
|
|
|
423
|
+ result: newResult.copyWith(
|
|
|
|
424
|
+ dailyStressPoints: dailyStressPoints,
|
|
|
|
425
|
+ sleepResults: sleepResults,
|
|
|
|
426
|
+ ),
|
|
|
|
427
|
+ hasExistingHrv: hasExistingHrv,
|
|
|
|
428
|
+ hasExistingSleep: hasExistingSleep,
|
|
|
|
429
|
+ );
|
|
|
|
430
|
+ } catch (error, stackTrace) {
|
|
|
|
431
|
+ _logError(
|
|
|
|
432
|
+ '$_calculateLogMarker calculate_failed userId=$userId',
|
|
|
|
433
|
+ error,
|
|
|
|
434
|
+ stackTrace,
|
|
|
|
435
|
+ );
|
|
|
|
436
|
+ return _OhosStoredCalculationResult(
|
|
|
|
437
|
+ result: emptyResult,
|
|
|
|
438
|
+ hasExistingHrv: hasExistingHrv,
|
|
|
|
439
|
+ hasExistingSleep: hasExistingSleep,
|
|
|
|
440
|
+ );
|
|
373
|
}
|
441
|
}
|
|
374
|
- return newResult.copyWith(
|
|
|
|
375
|
- dailyStressPoints: dailyStressPoints,
|
|
|
|
376
|
- sleepResults: sleepResults,
|
|
|
|
377
|
- );
|
|
|
|
378
|
}
|
442
|
}
|
|
379
|
|
443
|
|
|
380
|
Stream<HealthKitRawDataPoint> streamRawData({
|
444
|
Stream<HealthKitRawDataPoint> streamRawData({
|
|
@@ -562,6 +626,267 @@ class OHOSHealthRawDataCoreService { |
|
@@ -562,6 +626,267 @@ class OHOSHealthRawDataCoreService { |
|
562
|
}
|
626
|
}
|
|
563
|
}
|
627
|
}
|
|
564
|
|
628
|
|
|
|
|
629
|
+ void _scheduleResultUpload() {
|
|
|
|
630
|
+ if (!_uploadResultsAfterCalculation) return;
|
|
|
|
631
|
+ _logInfo('upload results scheduled');
|
|
|
|
632
|
+ unawaited(
|
|
|
|
633
|
+ _uploadResults().catchError((Object error, StackTrace stackTrace) {
|
|
|
|
634
|
+ _logError('upload results async failed', error, stackTrace);
|
|
|
|
635
|
+ return false;
|
|
|
|
636
|
+ }),
|
|
|
|
637
|
+ );
|
|
|
|
638
|
+ }
|
|
|
|
639
|
+
|
|
|
|
640
|
+ Future<void> _sendLocalNotificationsAfterCalculationSafely({
|
|
|
|
641
|
+ required HealthRawStressCalculationResult result,
|
|
|
|
642
|
+ required bool hasExistingHrv,
|
|
|
|
643
|
+ required bool hasExistingSleep,
|
|
|
|
644
|
+ }) async {
|
|
|
|
645
|
+ try {
|
|
|
|
646
|
+ await _sendLocalNotificationsAfterCalculation(
|
|
|
|
647
|
+ result: result,
|
|
|
|
648
|
+ hasExistingHrv: hasExistingHrv,
|
|
|
|
649
|
+ hasExistingSleep: hasExistingSleep,
|
|
|
|
650
|
+ );
|
|
|
|
651
|
+ } catch (error, stackTrace) {
|
|
|
|
652
|
+ _logError('send local notifications failed', error, stackTrace);
|
|
|
|
653
|
+ }
|
|
|
|
654
|
+ }
|
|
|
|
655
|
+
|
|
|
|
656
|
+ Future<void> _sendLocalNotificationsAfterCalculation({
|
|
|
|
657
|
+ required HealthRawStressCalculationResult result,
|
|
|
|
658
|
+ required bool hasExistingHrv,
|
|
|
|
659
|
+ required bool hasExistingSleep,
|
|
|
|
660
|
+ }) async {
|
|
|
|
661
|
+ _logInfo(
|
|
|
|
662
|
+ 'local_notification_start userId=${result.userId} '
|
|
|
|
663
|
+ 'hasExistingHrv=$hasExistingHrv hasExistingSleep=$hasExistingSleep '
|
|
|
|
664
|
+ 'hrv=${result.hrvStressPoints.length} '
|
|
|
|
665
|
+ 'realtime=${result.realtimeStressPoints.length} '
|
|
|
|
666
|
+ 'sleep=${result.sleepResults.length}',
|
|
|
|
667
|
+ );
|
|
|
|
668
|
+ if (result.hrvStressPoints.isEmpty &&
|
|
|
|
669
|
+ result.realtimeStressPoints.isEmpty &&
|
|
|
|
670
|
+ result.sleepResults.isEmpty) {
|
|
|
|
671
|
+ _logInfo('local_notification_skip reason=empty_calculation_result');
|
|
|
|
672
|
+ return;
|
|
|
|
673
|
+ }
|
|
|
|
674
|
+
|
|
|
|
675
|
+ final latestRealtimeStressPoint = result.realtimeStressPoints.isEmpty
|
|
|
|
676
|
+ ? null
|
|
|
|
677
|
+ : result.realtimeStressPoints.reduce(
|
|
|
|
678
|
+ (a, b) => a.rawEndTime >= b.rawEndTime ? a : b,
|
|
|
|
679
|
+ );
|
|
|
|
680
|
+ var notificationResult = result;
|
|
|
|
681
|
+ var realtimeWindow = const <HealthRawRealtimeStressPoint>[];
|
|
|
|
682
|
+ var realtimeSleepIntervals = const <HealthRawSleepInterval>[];
|
|
|
|
683
|
+ if (latestRealtimeStressPoint != null) {
|
|
|
|
684
|
+ if (latestRealtimeStressPoint.isWorkout ||
|
|
|
|
685
|
+ latestRealtimeStressPoint.isWorkoutRecovery) {
|
|
|
|
686
|
+ _logInfo(
|
|
|
|
687
|
+ 'local_notification_realtime_suppressed_before_build '
|
|
|
|
688
|
+ 'reason=workout_or_recovery '
|
|
|
|
689
|
+ 'rawEndTime=${latestRealtimeStressPoint.rawEndTime}',
|
|
|
|
690
|
+ );
|
|
|
|
691
|
+ await _recordRealtimeStressTimeSafely(
|
|
|
|
692
|
+ userId: result.userId,
|
|
|
|
693
|
+ recordTime: latestRealtimeStressPoint.rawEndTime,
|
|
|
|
694
|
+ );
|
|
|
|
695
|
+ notificationResult = result.copyWith(
|
|
|
|
696
|
+ realtimeStressPoints: const <HealthRawRealtimeStressPoint>[],
|
|
|
|
697
|
+ );
|
|
|
|
698
|
+ } else {
|
|
|
|
699
|
+ realtimeWindow = await _queryRealtimeStressNotificationWindowSafely(
|
|
|
|
700
|
+ userId: result.userId,
|
|
|
|
701
|
+ latestRawEndTime: latestRealtimeStressPoint.rawEndTime,
|
|
|
|
702
|
+ );
|
|
|
|
703
|
+ realtimeSleepIntervals = await _queryRealtimeStressSleepIntervalsSafely(
|
|
|
|
704
|
+ userId: result.userId,
|
|
|
|
705
|
+ latestRawEndTime: latestRealtimeStressPoint.rawEndTime,
|
|
|
|
706
|
+ );
|
|
|
|
707
|
+ }
|
|
|
|
708
|
+ }
|
|
|
|
709
|
+
|
|
|
|
710
|
+ final record = await _readLocalNotificationRecordSafely(result.userId);
|
|
|
|
711
|
+ final notifications = HealthRawLocalNotificationBuilder(l10n).build(
|
|
|
|
712
|
+ result: notificationResult,
|
|
|
|
713
|
+ hasExistingHrv: hasExistingHrv,
|
|
|
|
714
|
+ hasExistingSleep: hasExistingSleep,
|
|
|
|
715
|
+ realtimeWindow: realtimeWindow,
|
|
|
|
716
|
+ realtimeSleepIntervals: realtimeSleepIntervals,
|
|
|
|
717
|
+ lastRealtimeStressPushSendTime: record.lastRealtimeStressTime,
|
|
|
|
718
|
+ record: record,
|
|
|
|
719
|
+ );
|
|
|
|
720
|
+ _logInfo(
|
|
|
|
721
|
+ 'local_notification_built userId=${result.userId} '
|
|
|
|
722
|
+ 'count=${notifications.length} '
|
|
|
|
723
|
+ 'types=${notifications.map((e) => e.recordType.name).join(',')}',
|
|
|
|
724
|
+ );
|
|
|
|
725
|
+ if (notifications.isEmpty) return;
|
|
|
|
726
|
+
|
|
|
|
727
|
+ final sentNotifications = <HealthRawLocalNotification>[];
|
|
|
|
728
|
+ for (final notification in notifications) {
|
|
|
|
729
|
+ final sent = await _sendLocalNotificationSafely(
|
|
|
|
730
|
+ userId: result.userId,
|
|
|
|
731
|
+ notification: notification,
|
|
|
|
732
|
+ );
|
|
|
|
733
|
+ _logInfo(
|
|
|
|
734
|
+ 'local_notification_send_finished userId=${result.userId} '
|
|
|
|
735
|
+ 'sent=$sent type=${notification.recordType.name} '
|
|
|
|
736
|
+ 'recordTime=${notification.recordTime}',
|
|
|
|
737
|
+ );
|
|
|
|
738
|
+ if (sent) {
|
|
|
|
739
|
+ sentNotifications.add(notification);
|
|
|
|
740
|
+ }
|
|
|
|
741
|
+ }
|
|
|
|
742
|
+ _scheduleRealtimeStressServerPush(sentNotifications);
|
|
|
|
743
|
+ }
|
|
|
|
744
|
+
|
|
|
|
745
|
+ Future<HealthRawLocalNotificationRecord> _readLocalNotificationRecordSafely(
|
|
|
|
746
|
+ int userId,
|
|
|
|
747
|
+ ) async {
|
|
|
|
748
|
+ try {
|
|
|
|
749
|
+ return await _localNotificationDispatcher.readRecord(userId);
|
|
|
|
750
|
+ } catch (error, stackTrace) {
|
|
|
|
751
|
+ _logError('read local notification record failed', error, stackTrace);
|
|
|
|
752
|
+ return const HealthRawLocalNotificationRecord();
|
|
|
|
753
|
+ }
|
|
|
|
754
|
+ }
|
|
|
|
755
|
+
|
|
|
|
756
|
+ Future<void> _recordRealtimeStressTimeSafely({
|
|
|
|
757
|
+ required int userId,
|
|
|
|
758
|
+ required int recordTime,
|
|
|
|
759
|
+ }) async {
|
|
|
|
760
|
+ try {
|
|
|
|
761
|
+ await _localNotificationDispatcher.recordRealtimeStressTime(
|
|
|
|
762
|
+ userId: userId,
|
|
|
|
763
|
+ recordTime: recordTime,
|
|
|
|
764
|
+ );
|
|
|
|
765
|
+ } catch (error, stackTrace) {
|
|
|
|
766
|
+ _logError('record realtime stress push time failed', error, stackTrace);
|
|
|
|
767
|
+ }
|
|
|
|
768
|
+ }
|
|
|
|
769
|
+
|
|
|
|
770
|
+ Future<List<HealthRawRealtimeStressPoint>>
|
|
|
|
771
|
+ _queryRealtimeStressNotificationWindowSafely({
|
|
|
|
772
|
+ required int userId,
|
|
|
|
773
|
+ required int latestRawEndTime,
|
|
|
|
774
|
+ }) async {
|
|
|
|
775
|
+ try {
|
|
|
|
776
|
+ final points = await _localStore.queryRealtimeStressPoints(
|
|
|
|
777
|
+ userId: userId,
|
|
|
|
778
|
+ startTime: latestRawEndTime - Duration.secondsPerHour + 1,
|
|
|
|
779
|
+ endTime: latestRawEndTime,
|
|
|
|
780
|
+ );
|
|
|
|
781
|
+ _logInfo(
|
|
|
|
782
|
+ 'local_notification_realtime_window userId=$userId '
|
|
|
|
783
|
+ 'latestRawEndTime=$latestRawEndTime count=${points.length} '
|
|
|
|
784
|
+ 'valid=${points.where((e) => e.result >= 1 && e.result <= 100).length}',
|
|
|
|
785
|
+ );
|
|
|
|
786
|
+ return points;
|
|
|
|
787
|
+ } catch (error, stackTrace) {
|
|
|
|
788
|
+ _logError(
|
|
|
|
789
|
+ 'query realtime stress notification window failed',
|
|
|
|
790
|
+ error,
|
|
|
|
791
|
+ stackTrace,
|
|
|
|
792
|
+ );
|
|
|
|
793
|
+ return const <HealthRawRealtimeStressPoint>[];
|
|
|
|
794
|
+ }
|
|
|
|
795
|
+ }
|
|
|
|
796
|
+
|
|
|
|
797
|
+ Future<List<HealthRawSleepInterval>>
|
|
|
|
798
|
+ _queryRealtimeStressSleepIntervalsSafely({
|
|
|
|
799
|
+ required int userId,
|
|
|
|
800
|
+ required int latestRawEndTime,
|
|
|
|
801
|
+ }) async {
|
|
|
|
802
|
+ try {
|
|
|
|
803
|
+ final (startTime, endTime) = _dayRangeFromDateKey(
|
|
|
|
804
|
+ _dateKeyFromUnixSeconds(latestRawEndTime),
|
|
|
|
805
|
+ );
|
|
|
|
806
|
+ final sleepResults = await _localStore.querySleepResults(
|
|
|
|
807
|
+ userId: userId,
|
|
|
|
808
|
+ startTime: startTime,
|
|
|
|
809
|
+ endTime: endTime,
|
|
|
|
810
|
+ );
|
|
|
|
811
|
+ final intervals = sleepResults
|
|
|
|
812
|
+ .where((result) => result.startDate <= result.date)
|
|
|
|
813
|
+ .map(
|
|
|
|
814
|
+ (result) => (
|
|
|
|
815
|
+ startTime: result.startDate,
|
|
|
|
816
|
+ endTime: result.date,
|
|
|
|
817
|
+ ),
|
|
|
|
818
|
+ )
|
|
|
|
819
|
+ .toList(growable: false);
|
|
|
|
820
|
+ _logInfo(
|
|
|
|
821
|
+ 'local_notification_realtime_sleep_intervals userId=$userId '
|
|
|
|
822
|
+ 'latestRawEndTime=$latestRawEndTime count=${intervals.length}',
|
|
|
|
823
|
+ );
|
|
|
|
824
|
+ return intervals;
|
|
|
|
825
|
+ } catch (error, stackTrace) {
|
|
|
|
826
|
+ _logError(
|
|
|
|
827
|
+ 'query realtime stress sleep intervals failed',
|
|
|
|
828
|
+ error,
|
|
|
|
829
|
+ stackTrace,
|
|
|
|
830
|
+ );
|
|
|
|
831
|
+ return const <HealthRawSleepInterval>[];
|
|
|
|
832
|
+ }
|
|
|
|
833
|
+ }
|
|
|
|
834
|
+
|
|
|
|
835
|
+ Future<bool> _sendLocalNotificationSafely({
|
|
|
|
836
|
+ required int userId,
|
|
|
|
837
|
+ required HealthRawLocalNotification notification,
|
|
|
|
838
|
+ }) async {
|
|
|
|
839
|
+ try {
|
|
|
|
840
|
+ return await _localNotificationDispatcher.sendOne(
|
|
|
|
841
|
+ userId: userId,
|
|
|
|
842
|
+ notification: notification,
|
|
|
|
843
|
+ );
|
|
|
|
844
|
+ } catch (error, stackTrace) {
|
|
|
|
845
|
+ _logError('send local health notification failed', error, stackTrace);
|
|
|
|
846
|
+ return false;
|
|
|
|
847
|
+ }
|
|
|
|
848
|
+ }
|
|
|
|
849
|
+
|
|
|
|
850
|
+ void _scheduleRealtimeStressServerPush(
|
|
|
|
851
|
+ Iterable<HealthRawLocalNotification> sentNotifications,
|
|
|
|
852
|
+ ) {
|
|
|
|
853
|
+ final realtimeNotifications = sentNotifications.where(
|
|
|
|
854
|
+ (notification) =>
|
|
|
|
855
|
+ notification.recordType ==
|
|
|
|
856
|
+ HealthRawLocalNotificationRecordType.realtimeStress &&
|
|
|
|
857
|
+ notification.currentState != null,
|
|
|
|
858
|
+ );
|
|
|
|
859
|
+ for (final notification in realtimeNotifications) {
|
|
|
|
860
|
+ unawaited(_pushRealtimeStressServerNotification(notification));
|
|
|
|
861
|
+ }
|
|
|
|
862
|
+ }
|
|
|
|
863
|
+
|
|
|
|
864
|
+ Future<void> _pushRealtimeStressServerNotification(
|
|
|
|
865
|
+ HealthRawLocalNotification notification,
|
|
|
|
866
|
+ ) async {
|
|
|
|
867
|
+ final serverHealthApi = _serverHealthApi;
|
|
|
|
868
|
+ if (serverHealthApi == null) return;
|
|
|
|
869
|
+ try {
|
|
|
|
870
|
+ final result = await serverHealthApi.pushRealtimeStressNotification(
|
|
|
|
871
|
+ latestDataTime: notification.recordTime,
|
|
|
|
872
|
+ currentState: notification.currentState!,
|
|
|
|
873
|
+ );
|
|
|
|
874
|
+ if (result is AppFailure) {
|
|
|
|
875
|
+ _logError(
|
|
|
|
876
|
+ 'push realtime stress server notification failed',
|
|
|
|
877
|
+ result.error,
|
|
|
|
878
|
+ StackTrace.current,
|
|
|
|
879
|
+ );
|
|
|
|
880
|
+ }
|
|
|
|
881
|
+ } catch (error, stackTrace) {
|
|
|
|
882
|
+ _logError(
|
|
|
|
883
|
+ 'push realtime stress server notification failed',
|
|
|
|
884
|
+ error,
|
|
|
|
885
|
+ stackTrace,
|
|
|
|
886
|
+ );
|
|
|
|
887
|
+ }
|
|
|
|
888
|
+ }
|
|
|
|
889
|
+
|
|
565
|
Future<List<OhosHealthRawDataSyncResult>> _syncCalculationRawDataIfNeeded({
|
890
|
Future<List<OhosHealthRawDataSyncResult>> _syncCalculationRawDataIfNeeded({
|
|
566
|
required int startTime,
|
891
|
required int startTime,
|
|
567
|
required int endTime,
|
892
|
required int endTime,
|
|
@@ -577,6 +902,47 @@ class OHOSHealthRawDataCoreService { |
|
@@ -577,6 +902,47 @@ class OHOSHealthRawDataCoreService { |
|
577
|
);
|
902
|
);
|
|
578
|
}
|
903
|
}
|
|
579
|
|
904
|
|
|
|
|
905
|
+ Future<List<OhosHealthRawDataSyncResult>> _syncCalculationRawDataSafely({
|
|
|
|
906
|
+ required bool hasAuthorization,
|
|
|
|
907
|
+ required int startTime,
|
|
|
|
908
|
+ required int endTime,
|
|
|
|
909
|
+ }) async {
|
|
|
|
910
|
+ if (!hasAuthorization) {
|
|
|
|
911
|
+ _logInfo(
|
|
|
|
912
|
+ '$_calculateLogMarker sync_skipped reason=no_health_privacy_permission '
|
|
|
|
913
|
+ 'startTime=$startTime endTime=$endTime',
|
|
|
|
914
|
+ );
|
|
|
|
915
|
+ return const <OhosHealthRawDataSyncResult>[];
|
|
|
|
916
|
+ }
|
|
|
|
917
|
+ try {
|
|
|
|
918
|
+ return await _syncCalculationRawDataIfNeeded(
|
|
|
|
919
|
+ startTime: startTime,
|
|
|
|
920
|
+ endTime: endTime,
|
|
|
|
921
|
+ );
|
|
|
|
922
|
+ } catch (error, stackTrace) {
|
|
|
|
923
|
+ _logError(
|
|
|
|
924
|
+ '$_calculateLogMarker sync_failed_continue '
|
|
|
|
925
|
+ 'startTime=$startTime endTime=$endTime',
|
|
|
|
926
|
+ error,
|
|
|
|
927
|
+ stackTrace,
|
|
|
|
928
|
+ );
|
|
|
|
929
|
+ return const <OhosHealthRawDataSyncResult>[];
|
|
|
|
930
|
+ }
|
|
|
|
931
|
+ }
|
|
|
|
932
|
+
|
|
|
|
933
|
+ Future<bool> _hasHealthReadAuthorizationSafely() async {
|
|
|
|
934
|
+ try {
|
|
|
|
935
|
+ return await _hasHealthReadAuthorization();
|
|
|
|
936
|
+ } catch (error, stackTrace) {
|
|
|
|
937
|
+ _logError(
|
|
|
|
938
|
+ '$_calculateLogMarker privacy_authorization_failed_continue',
|
|
|
|
939
|
+ error,
|
|
|
|
940
|
+ stackTrace,
|
|
|
|
941
|
+ );
|
|
|
|
942
|
+ return false;
|
|
|
|
943
|
+ }
|
|
|
|
944
|
+ }
|
|
|
|
945
|
+
|
|
580
|
Future<bool> _hasHealthReadAuthorization() async {
|
946
|
Future<bool> _hasHealthReadAuthorization() async {
|
|
581
|
final checker = _healthReadAuthorizationChecker;
|
947
|
final checker = _healthReadAuthorizationChecker;
|
|
582
|
if (checker != null) {
|
948
|
if (checker != null) {
|
|
@@ -594,6 +960,19 @@ class OHOSHealthRawDataCoreService { |
|
@@ -594,6 +960,19 @@ class OHOSHealthRawDataCoreService { |
|
594
|
};
|
960
|
};
|
|
595
|
}
|
961
|
}
|
|
596
|
|
962
|
|
|
|
|
963
|
+ int? _latestRawDataTime(List<HealthKitRawDataPoint> points) {
|
|
|
|
964
|
+ if (points.isEmpty) return null;
|
|
|
|
965
|
+ return points
|
|
|
|
966
|
+ .map((point) => point.endTime)
|
|
|
|
967
|
+ .reduce((a, b) => a >= b ? a : b);
|
|
|
|
968
|
+ }
|
|
|
|
969
|
+
|
|
|
|
970
|
+ bool _needsNewResult(int? latestRawTime, int? latestResultTime) {
|
|
|
|
971
|
+ if (latestRawTime == null) return false;
|
|
|
|
972
|
+ if (latestResultTime == null) return true;
|
|
|
|
973
|
+ return latestRawTime > latestResultTime;
|
|
|
|
974
|
+ }
|
|
|
|
975
|
+
|
|
597
|
bool _logPrivacyAuthorizationFailure(Object error) {
|
976
|
bool _logPrivacyAuthorizationFailure(Object error) {
|
|
598
|
_logInfo('OHOS health privacy authorization check failed: $error');
|
977
|
_logInfo('OHOS health privacy authorization check failed: $error');
|
|
599
|
return false;
|
978
|
return false;
|
|
@@ -615,16 +994,31 @@ class OHOSHealthRawDataCoreService { |
|
@@ -615,16 +994,31 @@ class OHOSHealthRawDataCoreService { |
|
615
|
userId: userId,
|
994
|
userId: userId,
|
|
616
|
dates: affectedDates,
|
995
|
dates: affectedDates,
|
|
617
|
);
|
996
|
);
|
|
|
|
997
|
+ _logInfo(
|
|
|
|
998
|
+ '$_dailyStressLogMarker start userId=$userId nowSeconds=$nowSeconds '
|
|
|
|
999
|
+ 'incomingRealtime=${realtimePoints.length} '
|
|
|
|
1000
|
+ 'affectedDates=$affectedDates existingDates=$existingDates',
|
|
|
|
1001
|
+ );
|
|
618
|
final dailyStressPoints = <HealthRawDailyStressPoint>[];
|
1002
|
final dailyStressPoints = <HealthRawDailyStressPoint>[];
|
|
619
|
final emptyDates = <int>[];
|
1003
|
final emptyDates = <int>[];
|
|
620
|
for (final date in affectedDates) {
|
1004
|
for (final date in affectedDates) {
|
|
621
|
- if (date != todayDate && existingDates.contains(date)) continue;
|
1005
|
+ if (date != todayDate && existingDates.contains(date)) {
|
|
|
|
1006
|
+ _logInfo(
|
|
|
|
1007
|
+ '$_dailyStressLogMarker skip_existing userId=$userId date=$date',
|
|
|
|
1008
|
+ );
|
|
|
|
1009
|
+ continue;
|
|
|
|
1010
|
+ }
|
|
622
|
final (startTime, endTime) = _dayRangeFromDateKey(date);
|
1011
|
final (startTime, endTime) = _dayRangeFromDateKey(date);
|
|
623
|
final dayRealtimePoints = await _localStore.queryRealtimeStressPoints(
|
1012
|
final dayRealtimePoints = await _localStore.queryRealtimeStressPoints(
|
|
624
|
userId: userId,
|
1013
|
userId: userId,
|
|
625
|
startTime: startTime,
|
1014
|
startTime: startTime,
|
|
626
|
endTime: endTime,
|
1015
|
endTime: endTime,
|
|
627
|
);
|
1016
|
);
|
|
|
|
1017
|
+ _logInfo(
|
|
|
|
1018
|
+ '$_dailyStressLogMarker day_query userId=$userId date=$date '
|
|
|
|
1019
|
+ 'startTime=$startTime endTime=$endTime '
|
|
|
|
1020
|
+ 'dayRealtime=${dayRealtimePoints.length}',
|
|
|
|
1021
|
+ );
|
|
628
|
final point = HuaweiHealthRawStressCalculator.calculateDailyStressPoints(
|
1022
|
final point = HuaweiHealthRawStressCalculator.calculateDailyStressPoints(
|
|
629
|
userId: userId,
|
1023
|
userId: userId,
|
|
630
|
realtimePoints: dayRealtimePoints,
|
1024
|
realtimePoints: dayRealtimePoints,
|
|
@@ -634,9 +1028,17 @@ class OHOSHealthRawDataCoreService { |
|
@@ -634,9 +1028,17 @@ class OHOSHealthRawDataCoreService { |
|
634
|
).firstOrNull;
|
1028
|
).firstOrNull;
|
|
635
|
if (point == null) {
|
1029
|
if (point == null) {
|
|
636
|
emptyDates.add(date);
|
1030
|
emptyDates.add(date);
|
|
|
|
1031
|
+ _logInfo(
|
|
|
|
1032
|
+ '$_dailyStressLogMarker no_result userId=$userId date=$date',
|
|
|
|
1033
|
+ );
|
|
637
|
continue;
|
1034
|
continue;
|
|
638
|
}
|
1035
|
}
|
|
639
|
dailyStressPoints.add(point);
|
1036
|
dailyStressPoints.add(point);
|
|
|
|
1037
|
+ _logInfo(
|
|
|
|
1038
|
+ '$_dailyStressLogMarker result userId=$userId date=$date '
|
|
|
|
1039
|
+ 'stressValue=${point.stressValue} stressScore=${point.stressScore} '
|
|
|
|
1040
|
+ 'state=${point.state.value}',
|
|
|
|
1041
|
+ );
|
|
640
|
}
|
1042
|
}
|
|
641
|
await _localStore.upsertDailyStressPoints(
|
1043
|
await _localStore.upsertDailyStressPoints(
|
|
642
|
userId: userId,
|
1044
|
userId: userId,
|
|
@@ -646,6 +1048,10 @@ class OHOSHealthRawDataCoreService { |
|
@@ -646,6 +1048,10 @@ class OHOSHealthRawDataCoreService { |
|
646
|
userId: userId,
|
1048
|
userId: userId,
|
|
647
|
dates: emptyDates,
|
1049
|
dates: emptyDates,
|
|
648
|
);
|
1050
|
);
|
|
|
|
1051
|
+ _logInfo(
|
|
|
|
1052
|
+ '$_dailyStressLogMarker stored userId=$userId '
|
|
|
|
1053
|
+ 'stored=${dailyStressPoints.length} deletedEmptyDates=$emptyDates',
|
|
|
|
1054
|
+ );
|
|
649
|
return dailyStressPoints;
|
1055
|
return dailyStressPoints;
|
|
650
|
}
|
1056
|
}
|
|
651
|
|
1057
|
|
|
@@ -654,12 +1060,24 @@ class OHOSHealthRawDataCoreService { |
|
@@ -654,12 +1060,24 @@ class OHOSHealthRawDataCoreService { |
|
654
|
required List<HealthKitRawDataPoint> sleepIntervals,
|
1060
|
required List<HealthKitRawDataPoint> sleepIntervals,
|
|
655
|
required int? latestSleepResultTime,
|
1061
|
required int? latestSleepResultTime,
|
|
656
|
}) async {
|
1062
|
}) async {
|
|
657
|
- if (sleepIntervals.isEmpty) return const <HealthRawSleepResult>[];
|
1063
|
+ _logInfo(
|
|
|
|
1064
|
+ '$_sleepCalcLogMarker start userId=$userId '
|
|
|
|
1065
|
+ 'sleepIntervals=${sleepIntervals.length} '
|
|
|
|
1066
|
+ 'latestSleepResultTime=$latestSleepResultTime',
|
|
|
|
1067
|
+ );
|
|
|
|
1068
|
+ if (sleepIntervals.isEmpty) {
|
|
|
|
1069
|
+ _logInfo('$_sleepCalcLogMarker no_raw_sleep userId=$userId');
|
|
|
|
1070
|
+ return const <HealthRawSleepResult>[];
|
|
|
|
1071
|
+ }
|
|
658
|
final days = {
|
1072
|
final days = {
|
|
659
|
for (final interval in sleepIntervals)
|
1073
|
for (final interval in sleepIntervals)
|
|
660
|
DateTime.fromMillisecondsSinceEpoch(interval.endTime * 1000)
|
1074
|
DateTime.fromMillisecondsSinceEpoch(interval.endTime * 1000)
|
|
661
|
}.map((date) => DateTime(date.year, date.month, date.day)).toList()
|
1075
|
}.map((date) => DateTime(date.year, date.month, date.day)).toList()
|
|
662
|
..sort((a, b) => a.compareTo(b));
|
1076
|
..sort((a, b) => a.compareTo(b));
|
|
|
|
1077
|
+ _logInfo(
|
|
|
|
1078
|
+ '$_sleepCalcLogMarker days userId=$userId '
|
|
|
|
1079
|
+ 'days=${days.map(_dateKeyFromDateTime).toList()}',
|
|
|
|
1080
|
+ );
|
|
663
|
final results = <HealthRawSleepResult>[];
|
1081
|
final results = <HealthRawSleepResult>[];
|
|
664
|
for (final day in days) {
|
1082
|
for (final day in days) {
|
|
665
|
final calculation = HealthSleepCalculator.calculateDay(
|
1083
|
final calculation = HealthSleepCalculator.calculateDay(
|
|
@@ -669,10 +1087,30 @@ class OHOSHealthRawDataCoreService { |
|
@@ -669,10 +1087,30 @@ class OHOSHealthRawDataCoreService { |
|
669
|
final merged = calculation.mergeSleepTimeRange;
|
1087
|
final merged = calculation.mergeSleepTimeRange;
|
|
670
|
final score = calculation.score;
|
1088
|
final score = calculation.score;
|
|
671
|
final state = calculation.state;
|
1089
|
final state = calculation.state;
|
|
672
|
- if (merged == null || score == null || state == null) continue;
|
|
|
|
673
|
- if (!calculation.hasValidSleep) continue;
|
1090
|
+ if (merged == null || score == null || state == null) {
|
|
|
|
1091
|
+ _logInfo(
|
|
|
|
1092
|
+ '$_sleepCalcLogMarker skip_invalid userId=$userId '
|
|
|
|
1093
|
+ 'date=${_dateKeyFromDateTime(day)} '
|
|
|
|
1094
|
+ 'reason=missing_score_or_state '
|
|
|
|
1095
|
+ 'score=$score state=${state?.value}',
|
|
|
|
1096
|
+ );
|
|
|
|
1097
|
+ continue;
|
|
|
|
1098
|
+ }
|
|
|
|
1099
|
+ if (!calculation.hasValidSleep) {
|
|
|
|
1100
|
+ _logInfo(
|
|
|
|
1101
|
+ '$_sleepCalcLogMarker skip_invalid userId=$userId '
|
|
|
|
1102
|
+ 'date=${_dateKeyFromDateTime(day)} reason=no_valid_sleep '
|
|
|
|
1103
|
+ 'durationSeconds=${calculation.durationSeconds}',
|
|
|
|
1104
|
+ );
|
|
|
|
1105
|
+ continue;
|
|
|
|
1106
|
+ }
|
|
674
|
if (latestSleepResultTime != null &&
|
1107
|
if (latestSleepResultTime != null &&
|
|
675
|
merged.endTime <= latestSleepResultTime) {
|
1108
|
merged.endTime <= latestSleepResultTime) {
|
|
|
|
1109
|
+ _logInfo(
|
|
|
|
1110
|
+ '$_sleepCalcLogMarker skip_existing userId=$userId '
|
|
|
|
1111
|
+ 'date=${_dateKeyFromDateTime(day)} mergedEnd=${merged.endTime} '
|
|
|
|
1112
|
+ 'latestSleepResultTime=$latestSleepResultTime',
|
|
|
|
1113
|
+ );
|
|
676
|
continue;
|
1114
|
continue;
|
|
677
|
}
|
1115
|
}
|
|
678
|
results.add(
|
1116
|
results.add(
|
|
@@ -688,8 +1126,17 @@ class OHOSHealthRawDataCoreService { |
|
@@ -688,8 +1126,17 @@ class OHOSHealthRawDataCoreService { |
|
688
|
uploaded: false,
|
1126
|
uploaded: false,
|
|
689
|
),
|
1127
|
),
|
|
690
|
);
|
1128
|
);
|
|
|
|
1129
|
+ _logInfo(
|
|
|
|
1130
|
+ '$_sleepCalcLogMarker result userId=$userId '
|
|
|
|
1131
|
+ 'date=${_dateKeyFromDateTime(day)} start=${merged.startTime} '
|
|
|
|
1132
|
+ 'end=${merged.endTime} score=$score state=${state.value} '
|
|
|
|
1133
|
+ 'sleepMinutes=${calculation.summary.sleepMinutes}',
|
|
|
|
1134
|
+ );
|
|
691
|
}
|
1135
|
}
|
|
692
|
await _localStore.upsertSleepResults(userId: userId, results: results);
|
1136
|
await _localStore.upsertSleepResults(userId: userId, results: results);
|
|
|
|
1137
|
+ _logInfo(
|
|
|
|
1138
|
+ '$_sleepCalcLogMarker stored userId=$userId stored=${results.length}',
|
|
|
|
1139
|
+ );
|
|
693
|
return results;
|
1140
|
return results;
|
|
694
|
}
|
1141
|
}
|
|
695
|
|
1142
|
|
|
@@ -774,6 +1221,10 @@ class OHOSHealthRawDataCoreService { |
|
@@ -774,6 +1221,10 @@ class OHOSHealthRawDataCoreService { |
|
774
|
|
1221
|
|
|
775
|
static int _dateKeyFromUnixSeconds(int seconds) {
|
1222
|
static int _dateKeyFromUnixSeconds(int seconds) {
|
|
776
|
final date = DateTime.fromMillisecondsSinceEpoch(seconds * 1000);
|
1223
|
final date = DateTime.fromMillisecondsSinceEpoch(seconds * 1000);
|
|
|
|
1224
|
+ return _dateKeyFromDateTime(date);
|
|
|
|
1225
|
+ }
|
|
|
|
1226
|
+
|
|
|
|
1227
|
+ static int _dateKeyFromDateTime(DateTime date) {
|
|
777
|
return date.year * 10000 + date.month * 100 + date.day;
|
1228
|
return date.year * 10000 + date.month * 100 + date.day;
|
|
778
|
}
|
1229
|
}
|
|
779
|
|
1230
|
|
|
@@ -819,3 +1270,15 @@ class OHOSHealthRawDataCoreService { |
|
@@ -819,3 +1270,15 @@ class OHOSHealthRawDataCoreService { |
|
819
|
}
|
1270
|
}
|
|
820
|
}
|
1271
|
}
|
|
821
|
}
|
1272
|
}
|
|
|
|
1273
|
+
|
|
|
|
1274
|
+class _OhosStoredCalculationResult {
|
|
|
|
1275
|
+ const _OhosStoredCalculationResult({
|
|
|
|
1276
|
+ required this.result,
|
|
|
|
1277
|
+ required this.hasExistingHrv,
|
|
|
|
1278
|
+ required this.hasExistingSleep,
|
|
|
|
1279
|
+ });
|
|
|
|
1280
|
+
|
|
|
|
1281
|
+ final HealthRawStressCalculationResult result;
|
|
|
|
1282
|
+ final bool hasExistingHrv;
|
|
|
|
1283
|
+ final bool hasExistingSleep;
|
|
|
|
1284
|
+} |