Skip to content

Commit 8ab8f7d

Browse files
committed
fix bug notification stuck in processing on Android
1 parent 49332f6 commit 8ab8f7d

File tree

16 files changed

+124
-69
lines changed

16 files changed

+124
-69
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
## 1.5.2 - 25.10.2020
2+
3+
* Android: fix bug notification stuck in processing
4+
15
## 1.5.1 - 27.09.2020
26

37
* iOS: fix bug missing update download progress

android/src/main/java/vn/hunghd/flutterdownloader/DownloadWorker.java

Lines changed: 102 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import android.content.Context;
99
import android.content.Intent;
1010
import android.content.SharedPreferences;
11+
import android.content.pm.ApplicationInfo;
1112
import android.content.pm.PackageManager;
1213
import android.content.res.Resources;
1314
import android.graphics.BitmapFactory;
@@ -81,13 +82,13 @@ public class DownloadWorker extends Worker implements MethodChannel.MethodCallHa
8182
private MethodChannel backgroundChannel;
8283
private TaskDbHelper dbHelper;
8384
private TaskDao taskDao;
84-
private NotificationCompat.Builder builder;
8585
private boolean showNotification;
8686
private boolean clickToOpenDownloadedFile;
8787
private boolean debug;
8888
private int lastProgress = 0;
8989
private int primaryId;
9090
private String msgStarted, msgInProgress, msgCanceled, msgFailed, msgPaused, msgComplete;
91+
private long lastCallUpdateNotification = 0;
9192

9293
public DownloadWorker(@NonNull final Context context,
9394
@NonNull WorkerParameters params) {
@@ -183,10 +184,10 @@ public Result doWork() {
183184
DownloadTask task = taskDao.loadTask(getId().toString());
184185
primaryId = task.primaryId;
185186

186-
buildNotification(context);
187+
setupNotification(context);
187188

188-
updateNotification(context, filename == null ? url : filename, DownloadStatus.RUNNING, task.progress, null);
189-
taskDao.updateTask(getId().toString(), DownloadStatus.RUNNING, 0);
189+
updateNotification(context, filename == null ? url : filename, DownloadStatus.RUNNING, task.progress, null, false);
190+
taskDao.updateTask(getId().toString(), DownloadStatus.RUNNING, task.progress);
190191

191192
try {
192193
downloadFile(context, url, savedDir, filename, headers, isResume);
@@ -195,7 +196,7 @@ public Result doWork() {
195196
taskDao = null;
196197
return Result.success();
197198
} catch (Exception e) {
198-
updateNotification(context, filename == null ? url : filename, DownloadStatus.FAILED, -1, null);
199+
updateNotification(context, filename == null ? url : filename, DownloadStatus.FAILED, -1, null, true);
199200
taskDao.updateTask(getId().toString(), DownloadStatus.FAILED, lastProgress);
200201
e.printStackTrace();
201202
dbHelper = null;
@@ -340,7 +341,7 @@ private void downloadFile(Context context, String fileURL, String savedDir, Stri
340341
if ((lastProgress == 0 || progress > lastProgress + STEP_UPDATE || progress == 100)
341342
&& progress != lastProgress) {
342343
lastProgress = progress;
343-
updateNotification(context, filename, DownloadStatus.RUNNING, progress, null);
344+
updateNotification(context, filename, DownloadStatus.RUNNING, progress, null, false);
344345

345346
// This line possibly causes system overloaded because of accessing to DB too many ?!!!
346347
// but commenting this line causes tasks loaded from DB missing current downloading progress,
@@ -370,19 +371,19 @@ private void downloadFile(Context context, String fileURL, String savedDir, Stri
370371
}
371372
}
372373
}
373-
updateNotification(context, filename, status, progress, pendingIntent);
374+
updateNotification(context, filename, status, progress, pendingIntent, true);
374375
taskDao.updateTask(getId().toString(), status, progress);
375376

376377
log(isStopped() ? "Download canceled" : "File downloaded");
377378
} else {
378379
DownloadTask task = taskDao.loadTask(getId().toString());
379380
int status = isStopped() ? (task.resumable ? DownloadStatus.PAUSED : DownloadStatus.CANCELED) : DownloadStatus.FAILED;
380-
updateNotification(context, filename, status, -1, null);
381+
updateNotification(context, filename == null ? fileURL : filename, status, -1, null, true);
381382
taskDao.updateTask(getId().toString(), status, lastProgress);
382383
log(isStopped() ? "Download canceled" : "Server replied HTTP code: " + responseCode);
383384
}
384385
} catch (IOException e) {
385-
updateNotification(context, filename == null ? fileURL : filename, DownloadStatus.FAILED, -1, null);
386+
updateNotification(context, filename == null ? fileURL : filename, DownloadStatus.FAILED, -1, null, true);
386387
taskDao.updateTask(getId().toString(), DownloadStatus.FAILED, lastProgress);
387388
e.printStackTrace();
388389
} finally {
@@ -423,73 +424,113 @@ private void cleanUp() {
423424
}
424425
}
425426

426-
private void buildNotification(Context context) {
427+
private int getNotificationIconRes() {
428+
try {
429+
ApplicationInfo applicationInfo = getApplicationContext().getPackageManager().getApplicationInfo(getApplicationContext().getPackageName(), PackageManager.GET_META_DATA);
430+
int appIconResId = applicationInfo.icon;
431+
return applicationInfo.metaData.getInt("vn.hunghd.flutterdownloader.NOTIFICATION_ICON", appIconResId);
432+
} catch (PackageManager.NameNotFoundException e) {
433+
e.printStackTrace();
434+
}
435+
return 0;
436+
}
437+
438+
private void setupNotification(Context context) {
439+
if (!showNotification) return;
427440
// Make a channel if necessary
428441
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
429442
// Create the NotificationChannel, but only on API 26+ because
430443
// the NotificationChannel class is new and not in the support library
431444

432-
CharSequence name = context.getApplicationInfo().loadLabel(context.getPackageManager());
433-
int importance = NotificationManager.IMPORTANCE_DEFAULT;
434-
NotificationChannel channel = new NotificationChannel(CHANNEL_ID, name, importance);
445+
446+
Resources res = getApplicationContext().getResources();
447+
String channelName = res.getString(R.string.flutter_downloader_notification_channel_name);
448+
String channelDescription = res.getString(R.string.flutter_downloader_notification_channel_description);
449+
int importance = NotificationManager.IMPORTANCE_LOW;
450+
NotificationChannel channel = new NotificationChannel(CHANNEL_ID, channelName, importance);
451+
channel.setDescription(channelDescription);
435452
channel.setSound(null, null);
436453

437454
// Add the channel
438-
NotificationManager notificationManager = context.getSystemService(NotificationManager.class);
439-
440-
if (notificationManager != null) {
441-
notificationManager.createNotificationChannel(channel);
442-
}
455+
NotificationManagerCompat notificationManager = NotificationManagerCompat.from(context);
456+
notificationManager.createNotificationChannel(channel);
443457
}
444-
445-
// Create the notification
446-
builder = new NotificationCompat.Builder(context, CHANNEL_ID)
447-
// .setSmallIcon(R.drawable.ic_download)
448-
.setOnlyAlertOnce(true)
449-
.setAutoCancel(true)
450-
.setPriority(NotificationCompat.PRIORITY_DEFAULT);
451-
452458
}
453459

454-
private void updateNotification(Context context, String title, int status, int progress, PendingIntent intent) {
455-
builder.setContentTitle(title);
456-
builder.setContentIntent(intent);
457-
boolean shouldUpdate = false;
458-
459-
if (status == DownloadStatus.RUNNING) {
460-
shouldUpdate = true;
461-
builder.setContentText(progress == 0 ? msgStarted : msgInProgress)
462-
.setProgress(100, progress, progress == 0);
463-
builder.setOngoing(true)
464-
.setSmallIcon(android.R.drawable.stat_sys_download);
465-
} else if (status == DownloadStatus.CANCELED) {
466-
shouldUpdate = true;
467-
builder.setContentText(msgCanceled).setProgress(0, 0, false);
468-
builder.setOngoing(false)
469-
.setSmallIcon(android.R.drawable.stat_sys_download_done);
470-
} else if (status == DownloadStatus.FAILED) {
471-
shouldUpdate = true;
472-
builder.setContentText(msgFailed).setProgress(0, 0, false);
473-
builder.setOngoing(false)
474-
.setSmallIcon(android.R.drawable.stat_sys_download_done);
475-
} else if (status == DownloadStatus.PAUSED) {
476-
shouldUpdate = true;
477-
builder.setContentText(msgPaused).setProgress(0, 0, false);
478-
builder.setOngoing(false)
479-
.setSmallIcon(android.R.drawable.stat_sys_download_done);
480-
} else if (status == DownloadStatus.COMPLETE) {
481-
shouldUpdate = true;
482-
builder.setContentText(msgComplete).setProgress(0, 0, false);
483-
builder.setOngoing(false)
484-
.setSmallIcon(android.R.drawable.stat_sys_download_done);
485-
}
460+
private void updateNotification(Context context, String title, int status, int progress, PendingIntent intent, boolean finalize) {
461+
sendUpdateProcessEvent(status, progress);
486462

487463
// Show the notification
488-
if (showNotification && shouldUpdate) {
464+
if (showNotification) {
465+
// Create the notification
466+
NotificationCompat.Builder builder = new NotificationCompat.Builder(context, CHANNEL_ID).
467+
setContentTitle(title)
468+
.setContentIntent(intent)
469+
.setOnlyAlertOnce(true)
470+
.setAutoCancel(true)
471+
.setPriority(NotificationCompat.PRIORITY_LOW);
472+
473+
if (status == DownloadStatus.RUNNING) {
474+
if (progress <= 0) {
475+
builder.setContentText(msgStarted)
476+
.setProgress(0, 0, false);
477+
builder.setOngoing(false)
478+
.setSmallIcon(getNotificationIconRes());
479+
} else if (progress < 100) {
480+
builder.setContentText(msgInProgress)
481+
.setProgress(100, progress, false);
482+
builder.setOngoing(true)
483+
.setSmallIcon(android.R.drawable.stat_sys_download);
484+
} else {
485+
builder.setContentText(msgComplete).setProgress(0, 0, false);
486+
builder.setOngoing(false)
487+
.setSmallIcon(android.R.drawable.stat_sys_download_done);
488+
}
489+
} else if (status == DownloadStatus.CANCELED) {
490+
builder.setContentText(msgCanceled).setProgress(0, 0, false);
491+
builder.setOngoing(false)
492+
.setSmallIcon(android.R.drawable.stat_sys_download_done);
493+
} else if (status == DownloadStatus.FAILED) {
494+
builder.setContentText(msgFailed).setProgress(0, 0, false);
495+
builder.setOngoing(false)
496+
.setSmallIcon(android.R.drawable.stat_sys_download_done);
497+
} else if (status == DownloadStatus.PAUSED) {
498+
builder.setContentText(msgPaused).setProgress(0, 0, false);
499+
builder.setOngoing(false)
500+
.setSmallIcon(android.R.drawable.stat_sys_download_done);
501+
} else if (status == DownloadStatus.COMPLETE) {
502+
builder.setContentText(msgComplete).setProgress(0, 0, false);
503+
builder.setOngoing(false)
504+
.setSmallIcon(android.R.drawable.stat_sys_download_done);
505+
} else {
506+
builder.setProgress(0, 0, false);
507+
builder.setOngoing(false).setSmallIcon(getNotificationIconRes());
508+
}
509+
510+
// Note: Android applies a rate limit when updating a notification.
511+
// If you post updates to a notification too frequently (many in less than one second),
512+
// the system might drop some updates. (https://developer.android.com/training/notify-user/build-notification#Updating)
513+
//
514+
// If this is progress update, it's not much important if it is dropped because there're still incoming updates later
515+
// If this is the final update, it must be success otherwise the notification will be stuck at the processing state
516+
// In order to ensure the final one is success, we check and sleep a second if need.
517+
if (System.currentTimeMillis() - lastCallUpdateNotification < 1000) {
518+
if (finalize) {
519+
log("Update too frequently!!!!, but it is the final update, we should sleep a second to ensure the update call can be processed");
520+
try {
521+
Thread.sleep(1000);
522+
} catch (InterruptedException e) {
523+
e.printStackTrace();
524+
}
525+
} else {
526+
log("Update too frequently!!!!, this should be dropped");
527+
return;
528+
}
529+
}
530+
log("Update notification: {notificationId: " + primaryId + ", title: " + title + ", status: " + status + ", progress: " + progress + "}");
489531
NotificationManagerCompat.from(context).notify(primaryId, builder.build());
532+
lastCallUpdateNotification = System.currentTimeMillis();
490533
}
491-
492-
sendUpdateProcessEvent(status, progress);
493534
}
494535

495536
private void sendUpdateProcessEvent(int status, int progress) {

android/src/main/res/values/strings.xml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,6 @@
66
<string name="flutter_downloader_notification_failed">failed</string>
77
<string name="flutter_downloader_notification_complete">complete</string>
88
<string name="flutter_downloader_notification_paused">paused</string>
9+
<string name="flutter_downloader_notification_channel_name">Downloader</string>
10+
<string name="flutter_downloader_notification_channel_description">Display download progress</string>
911
</resources>

example/android/app/src/main/AndroidManifest.xml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313

1414
<application
1515
android:name="io.flutter.app.FlutterApplication"
16-
android:label="example"
16+
android:label="Flutter Downloader"
1717
android:usesCleartextTraffic="true"
1818
android:icon="@mipmap/ic_launcher">
1919
<activity
@@ -60,5 +60,9 @@
6060
android:value="5" />
6161
</provider>
6262

63+
<meta-data
64+
android:name="vn.hunghd.flutterdownloader.NOTIFICATION_ICON"
65+
android:resource="@drawable/ic_stat_flutter" />
66+
6367
</application>
6468
</manifest>
975 Bytes
Loading
595 Bytes
Loading
1.01 KB
Loading

0 commit comments

Comments
 (0)