PeriodicWorker doesn't show notifications

69 Views Asked by At

I'm using a PeriodicWorker to make a daily backup of my app's data, that is, uploading mu local data to a cloud storage service. However when I use setForegroundAsync(foregroundInfo); it first, shows me a warning (startForegroundService() not allowed due to mAllowStartForeground false: service com.mydomain/androidx.work.impl.foreground.SystemForegroundService) and at the of the process, it gives me the error below

Unable to stop foreground service
android.app.BackgroundServiceStartNotAllowedException: Not allowed to start service Intent { act=ACTION_STOP_FOREGROUND cmp=com.mydomain/androidx.work.impl.foreground.SystemForegroundService }: app is in background uid UidRecord{dff373e u0a386 TRNB bg:+3m49s885ms idle change:procadj procs:0 seq(7594403,7591739)} caps=------
    at android.app.ContextImpl.startServiceCommon(ContextImpl.java:1945)
    at android.app.ContextImpl.startService(ContextImpl.java:1900)
    at android.content.ContextWrapper.startService(ContextWrapper.java:825)
    at androidx.work.impl.Processor.stopForegroundService(Processor.java:425)
    at androidx.work.impl.Processor.stopForeground(Processor.java:303)
    at androidx.work.impl.WorkerWrapper.resolve(WorkerWrapper.java:464)
    at androidx.work.impl.WorkerWrapper.resetPeriodicAndResolve(WorkerWrapper.java:577)
    at androidx.work.impl.WorkerWrapper.handleResult(WorkerWrapper.java:480)
    at androidx.work.impl.WorkerWrapper.onWorkFinished(WorkerWrapper.java:358)
    at androidx.work.impl.WorkerWrapper$2.run(WorkerWrapper.java:335)
    at androidx.work.impl.utils.SerialExecutorImpl$Task.run(SerialExecutorImpl.java:96)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:644)
    at java.lang.Thread.run(Thread.java:1012)

The "funny" part is the process runs perfectly fine, all the data are perfectly copied to the cloud and nothing is wrong, except the notification that is not shown.

Does anybody has any idea of how I can show the notification even though the application is in the background?

Here is my Worker's code.

public class DatabaseCloudBackupWorker extends Worker {
    public static final String TAG = DatabaseCloudBackupWorker.class.getName();

    @Override
    public Result doWork() {
        Result result = Result.failure();

        Log.i(TAG, "Starting Database Backup");
        this.showProgressNotification();

        try {
            uploadData()
            result = Result.success();

        } catch (Exception e) {
            Log.e(TAG, "Error")

        }

        return result;
    }

    private void showProgressNotification() {
        Log.i(TAG, "Showing Database Backup Progress Notification");

        Notification notification = this.newProgressNotification();

        ForegroundInfo foregroundInfo;

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
            foregroundInfo = new ForegroundInfo(PROGRESS_NOTIFICATION_ID, notification, ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC);
        } else {
            foregroundInfo = new ForegroundInfo(PROGRESS_NOTIFICATION_ID, notification);
        }

        this.setForegroundAsync(foregroundInfo);

    }

    private Notification newProgressNotification() {
        Notification notification = new NotificationCompat.Builder(MyApp.getAppContext(), MyNotificationManager.CHANNEL_ID)
                .setSmallIcon(R.mipmap.ic_launcher)
                .setOnlyAlertOnce(true)
                .setOngoing(true)
                .setPriority(NotificationCompat.PRIORITY_HIGH)
                .setProgress(0, 0, true)
                .setForegroundServiceBehavior(NotificationCompat.FOREGROUND_SERVICE_IMMEDIATE)
                .setContentTitle("Database Backup")
                .setContentText("Database Backup in Progress")
                .build();

        return notification;
    }
}

Here's the session of my manifest where I set the type of the foreground service

        <service
            android:name="androidx.work.impl.foreground.SystemForegroundService"
            android:foregroundServiceType="dataSync"
            tools:node="merge" />

Here's where I create the PeriodicWorkRequest


            // Creating Work Request
            Constraints constraints = new Constraints.Builder().setRequiredNetworkType(NetworkType.UNMETERED).build();
            PeriodicWorkRequest workRequest = new PeriodicWorkRequest.Builder(DatabaseCloudBackupWorker.class, 1, TimeUnit.DAYS)
                    .setInitialDelay(initialDelayInMinutes, TimeUnit.MINUTES)
                    .addTag(DatabaseCloudBackupWorker.TAG_PERIODIC_WORKER)
                    .setConstraints(constraints)
                    .setInputData(inputData)
                    .build();

            //
            WorkManager workManager = WorkManager.getInstance(MyApp.getAppContext());

            Log.i(TAG, "Enqueuing DatabaseCloudBackupWorker");
            Operation operation = workManager.enqueueUniquePeriodicWork(DatabaseCloudBackupWorker.NAME_PERIODIC, ExistingPeriodicWorkPolicy.UPDATE, workRequest);

2

There are 2 best solutions below

6
Muhammad Shah On

Use below code for notifications.

If your Backup Data is working fine in Background, then use below code for notifications, it will work

You have to create channel for notifications in Android 8.1 and later.

 private static final int NOTIFICATION_ID = 1;
 private static final String CHANNEL_ID = "MyChannelId";
 
 // Create a notification channel
 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
    NotificationChannel channel = new NotificationChannel(CHANNEL_ID, "My Channel", NotificationManager.IMPORTANCE_LOW);
     NotificationManager notificationManager = getSystemService(NotificationManager.class);
    notificationManager.createNotificationChannel(channel);
    }

Notification notification = buildNotification();
startForeground(NOTIFICATION_ID, notification);

 @RequiresApi(api = Build.VERSION_CODES.M)
private Notification buildNotification() {

    Intent fullScreenIntent = new Intent(this, HomeActivity.class);
    PendingIntent fullScreenPendingIntent = PendingIntent.getActivity(this, 0,
            fullScreenIntent, PendingIntent.FLAG_IMMUTABLE);

    NotificationCompat.Builder notificationBuilder =
            new NotificationCompat.Builder(this, CHANNEL_ID)
                    .setSmallIcon(R.drawable.ic_notification)
                    .setContentTitle("Title")
                    .setContentText("Text")
                    .setPriority(NotificationCompat.PRIORITY_HIGH)
                    .setCategory(NotificationCompat.CATEGORY_CALL)

                    // Use a full-screen intent only for the highest-priority alerts where you
                    // have an associated activity that you would like to launch after the user
                    // interacts with the notification. Also, if your app targets Android 10
                    // or higher, you need to request the USE_FULL_SCREEN_INTENT permission in
                    // order for the platform to invoke this notification.
                    .setFullScreenIntent(fullScreenPendingIntent, true);

    return notificationBuilder.build();
}
2
Muhammad Shah On

Post Notification permission, In Android 13 and later versions first you have to allow Post Notification permission.

In manifest, add this permission

 <uses-permission android:name="android.permission.POST_NOTIFICATIONS" />

After this, add the below code in your desired Activity onCreate method and declare variables outside the onCreate method.

@SuppressLint("InlinedApi")
private static final String[] NOTIFICATION_PERMISSION = {
        Manifest.permission.POST_NOTIFICATIONS
};

private static final int NOTIFICATION_REQUEST_PERMISSIONS = 4;

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU &&
            ActivityCompat.checkSelfPermission(getApplicationContext(),
                    Manifest.permission.POST_NOTIFICATIONS) != PackageManager.PERMISSION_GRANTED) {
        requestPermissions(NOTIFICATION_PERMISSION,
                NOTIFICATION_REQUEST_PERMISSIONS);
    }