Sfoglia il codice sorgente

fix(mobile,Android): throttle detail progress notifications & wait on foregroundInfo (#907)

Fynn Petersen-Frey 2 anni fa
parent
commit
dcefd53bfe

+ 16 - 2
mobile/android/app/src/main/kotlin/com/example/mobile/BackupWorker.kt

@@ -50,6 +50,7 @@ class BackupWorker(ctx: Context, params: WorkerParameters) : ListenableWorker(ct
     private var timeBackupStarted: Long = 0L
     private var notificationBuilder: NotificationCompat.Builder? = null
     private var notificationDetailBuilder: NotificationCompat.Builder? = null
+    private var fgFuture: ListenableFuture<Void>? = null
 
     override fun startWork(): ListenableFuture<ListenableWorker.Result> {
 
@@ -112,6 +113,7 @@ class BackupWorker(ctx: Context, params: WorkerParameters) : ListenableWorker(ct
         Handler(Looper.getMainLooper()).postAtFrontOfQueue {
             backgroundChannel.invokeMethod("systemStop", null)
         }
+        waitOnSetForegroundAsync()
         // cannot await/get(block) on resolvableFuture as its already cancelled (would throw CancellationException)
         // instead, wait for 5 seconds until forcefully stopping backup work
         Handler(Looper.getMainLooper()).postDelayed({
@@ -119,6 +121,17 @@ class BackupWorker(ctx: Context, params: WorkerParameters) : ListenableWorker(ct
         }, 5000)
     }
 
+    private fun waitOnSetForegroundAsync() {
+        val fgFuture = this.fgFuture
+        if (fgFuture != null && !fgFuture.isCancelled() && !fgFuture.isDone()) {
+            try {
+                fgFuture.get(500, TimeUnit.MILLISECONDS)
+            }
+            catch (e: Exception) {
+                // ignored, there is nothing to be done
+            }
+        }
+    }
 
     private fun stopEngine(result: Result?) {
         if (result != null) {
@@ -128,6 +141,7 @@ class BackupWorker(ctx: Context, params: WorkerParameters) : ListenableWorker(ct
         engine?.destroy()
         engine = null
         clearBackgroundNotification()
+        waitOnSetForegroundAsync()
     }
 
     override fun onMethodCall(call: MethodCall, r: MethodChannel.Result) {
@@ -207,8 +221,8 @@ class BackupWorker(ctx: Context, params: WorkerParameters) : ListenableWorker(ct
 
     private fun showInfo(notification: Notification, isDetail: Boolean = false) {
         val id = if(isDetail) NOTIFICATION_DETAIL_ID else NOTIFICATION_ID
-        if (isIgnoringBatteryOptimizations) {
-            setForegroundAsync(ForegroundInfo(id, notification))
+        if (isIgnoringBatteryOptimizations && !isDetail) {
+            fgFuture = setForegroundAsync(ForegroundInfo(id, notification))
         } else {
             notificationManager.notify(id, notification)
         }

+ 44 - 16
mobile/lib/modules/backup/background_service/background.service.dart

@@ -43,8 +43,9 @@ class BackgroundService {
   bool _errorGracePeriodExceeded = true;
   int _uploadedAssetsCount = 0;
   int _assetsToUploadCount = 0;
-  int _lastDetailProgressUpdate = 0;
   String _lastPrintedProgress = "";
+  late final _Throttle _throttleNotificationUpdates =
+      _Throttle(_updateDetailProgress, const Duration(milliseconds: 400));
 
   bool get isBackgroundInitialized {
     return _isBackgroundInitialized;
@@ -447,21 +448,20 @@ class BackgroundService {
   }
 
   void _onProgress(int sent, int total) {
-    final int now = Timeline.now;
-    // limit updates to 10 per second (or Android drops important notifications)
-    if (now > _lastDetailProgressUpdate + 100000) {
-      final String msg = _humanReadableBytesProgress(sent, total);
-      // only update if message actually differs (to stop many useless notification updates on large assets or slow connections)
-      if (msg != _lastPrintedProgress) {
-        _lastDetailProgressUpdate = now;
-        _lastPrintedProgress = msg;
-        _updateNotification(
-          progress: sent,
-          max: total,
-          isDetail: true,
-          content: msg,
-        );
-      }
+    _throttleNotificationUpdates(sent, total);
+  }
+
+  void _updateDetailProgress(int sent, int total) {
+    final String msg = _humanReadableBytesProgress(sent, total);
+    // only update if message actually differs (to stop many useless notification updates on large assets or slow connections)
+    if (msg != _lastPrintedProgress) {
+      _lastPrintedProgress = msg;
+      _updateNotification(
+        progress: sent,
+        max: total,
+        isDetail: true,
+        content: msg,
+      );
     }
   }
 
@@ -532,6 +532,34 @@ class BackgroundService {
   }
 }
 
+class _Throttle {
+  _Throttle(this._fun, Duration interval) : _interval = interval.inMicroseconds;
+  final void Function(int, int) _fun;
+  final int _interval;
+  int _invokedAt = 0;
+  Timer? _timer;
+  int _progress = 0;
+  int _total = 0;
+
+  void call(int progress, int total) {
+    final time = Timeline.now;
+    _progress = progress;
+    _total = total;
+    if (time > _invokedAt + _interval) {
+      _timer?.cancel();
+      _onTimeElapsed();
+    } else {
+      _timer ??= Timer(Duration(microseconds: _interval), _onTimeElapsed);
+    }
+  }
+
+  void _onTimeElapsed() {
+    _invokedAt = Timeline.now;
+    _fun(_progress, _total);
+    _timer = null;
+  }
+}
+
 /// entry point called by Kotlin/Java code; needs to be a top-level function
 @pragma('vm:entry-point')
 void _nativeEntry() {