88import android .content .Context ;
99import android .content .Intent ;
1010import android .content .SharedPreferences ;
11+ import android .content .pm .ApplicationInfo ;
1112import android .content .pm .PackageManager ;
1213import android .content .res .Resources ;
1314import 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 ) {
0 commit comments