Foreground Service Scheduling Issue: AlarmManager Fails to start Foreground Service When App is Not in the Background

294 Views Asked by At

I'm encountering an issue with scheduling a foreground service using AlarmManager in my Android application. Despite setting up the code to trigger a foreground service at specific times using alarms, the alarms are unable to start foreground Service when the app is not in the background (it works fine when the app is in the foreground or in the background).

I have a requirement in my app to execute a foreground service at a specific time range of the day. To achieve this, I'm using AlarmManager to schedule the service to start and stop at the desired intervals.

and what are the different ways other than alarmManager, to schedule a foreground service.

TimeBasedScheduler Class:

class TimeBasedScheduler : BroadcastReceiver() {
    private val TAG = "TimeBasedScheduler"

    override fun onReceive(context: Context, intent: Intent) {
        val action = intent.action
        Log.d(TAG, "Received action: $action")

        if (action != null) {
            when (action) {
                "START_SERVICE" -> {
                    Log.d(TAG, "Initializing socket for START_SERVICE")
                    initializeSocket(context)
                    // Now execution is started and resumed
                    LocalPrefrenceUtils.insertDataInBoolean(context, AppConstants.BACKGROUND_WORK_ALLOWED_KEY_FOR_LOCAL_PREFERENCE, true)
                    LocalPrefrenceUtils.insertDataInBoolean(context, AppConstants.BACKGROUND_WORK_TEMPORARILY_PAUSED_KEY_FOR_LOCAL_PREFERENCE, false
                    )
                }
                "STOP_SERVICE" -> {
                    Log.d(TAG, "Stopping service for STOP_SERVICE")
                    LocalPrefrenceUtils.insertDataInBoolean(
                        context,
                        AppConstants.BACKGROUND_WORK_TEMPORARILY_PAUSED_KEY_FOR_LOCAL_PREFERENCE,
                        true
                    )
                    val stopIntent = Intent(context, SocketManagerService::class.java)
                    context.stopService(stopIntent)
                }
            }
        }
    }

    private fun initializeSocket(context: Context?) {
        Log.d(TAG, "Initializing socket")
            val intent = Intent(context, SocketManagerService::class.java)
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
                Log.d(TAG, "Starting foreground service")
                context?.startForegroundService(intent)
            } else {
                Log.d(TAG, "Starting service")
                context?.startService(intent)
            }

    }
}

SettingActivity Class:

private fun setupForegroundServiceAlarms() {
        val TAG = "TaskBasedScheduler"
        // Get the start and end times from preferences
        val startTime = LocalPrefrenceUtils.getStartTime(this)
        val endTime = LocalPrefrenceUtils.getEndTime(this)

        Log.d(TAG, "Start Time: $startTime")
        Log.d(TAG, "End Time: $endTime")

        // Calculate start and end times based on step sizes and special cases for value 48
        val startHour = 7 // Starting hour (7:00 am)
        val stepSizeMinutes = 30 // Step size in minutes

        val startHourForAlarms = startHour + (stepSizeMinutes * startTime) / 60
        val endHourForAlarms = if (endTime == 48) startHour + (stepSizeMinutes * endTime) / 60 - 1 else startHour + (stepSizeMinutes * endTime) / 60

        val startMinute = (stepSizeMinutes * startTime) % 60
        val endMinute = if (endTime == 48) ((stepSizeMinutes * endTime) % 60) - 1 else ((stepSizeMinutes * endTime) % 60)

        // Set the Calendar instances for start and end times
        val startCalendar = Calendar.getInstance().apply {
            timeInMillis = System.currentTimeMillis()
            set(Calendar.HOUR_OF_DAY, startHourForAlarms)
            set(Calendar.MINUTE, startMinute)
            set(Calendar.SECOND, 0)
        }

        val endCalendar = Calendar.getInstance().apply {
            timeInMillis = System.currentTimeMillis()
            set(Calendar.HOUR_OF_DAY, endHourForAlarms)
            set(Calendar.MINUTE, endMinute)
            set(Calendar.SECOND, 0)
        }

        Log.d(TAG, "Start Calendar Time: ${startCalendar.time}")
        Log.d(TAG, "End Calendar Time: ${endCalendar.time}")

        // Cancel previously set alarms
        alarmManager.cancel(startPendingIntent)
        alarmManager.cancel(endPendingIntent)

        Log.d(TAG, "Canceled previous alarms")

        // Create intents for starting and stopping the service
        val startIntent = Intent(this, TimeBasedScheduler::class.java).apply {
            action = "START_SERVICE"
        }
        val endIntent = Intent(this, TimeBasedScheduler::class.java).apply {
            action = "STOP_SERVICE"
        }

        // Create pending intents for start and end actions
        startPendingIntent = PendingIntent.getBroadcast(this, 0, startIntent, PendingIntent.FLAG_IMMUTABLE)
        endPendingIntent = PendingIntent.getBroadcast(this, 1, endIntent, PendingIntent.FLAG_IMMUTABLE)

        Log.d(TAG, "Created pending intents")

        // Set alarms using AlarmManager for daily repetition
        alarmManager.setInexactRepeating(
            AlarmManager.RTC_WAKEUP,
            startCalendar.timeInMillis,
            AlarmManager.INTERVAL_DAY,
            startPendingIntent
        )

        alarmManager.setInexactRepeating(
            AlarmManager.RTC_WAKEUP,
            endCalendar.timeInMillis,
            AlarmManager.INTERVAL_DAY,
            endPendingIntent
        )

        Log.d(TAG, "Alarms set for start: ${startCalendar.time}, end: ${endCalendar.time}")
    }

and manifest related code:

<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
    <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
    <uses-permission android:name="android.permission.WAKE_LOCK" />
    <uses-permission android:name="com.android.alarm.permission.SET_ALARM" />

<receiver android:name=".schedulers.TimeBasedScheduler"
            android:enabled="true"
            android:exported="true">
            <intent-filter>
                <action android:name="START_SERVICE" />
                <action android:name="STOP_SERVICE" />
            </intent-filter>
        </receiver>

works perfectly fine when the app is in the foreground or background:

enter image description here

Receiver when the app is in the foreground or background: enter image description here

I've checked the logs using Logcat, and it doesn't show any error messages related to alarms or the foreground service not starting.

I'm seeking guidance on why the alarms are not triggering the foreground service as expected. I'd appreciate any insights, suggestions, or potential solutions to resolve this issue.

Thank you for your help.

2

There are 2 best solutions below

1
Ehsan Setayesh On BEST ANSWER
try
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
    alarmManager.setExactAndAllowWhileIdle(
        AlarmManager.RTC_WAKEUP,
        startCalendar.timeInMillis,
        startPendingIntent
    )

    alarmManager.setExactAndAllowWhileIdle(
        AlarmManager.RTC_WAKEUP,
        endCalendar.timeInMillis,
        endPendingIntent
    )
} else {
    // For versions below M, use setExact
    alarmManager.setExact(
        AlarmManager.RTC_WAKEUP,
        startCalendar.timeInMillis,
        startPendingIntent
    )

    alarmManager.setExact(
        AlarmManager.RTC_WAKEUP,
        endCalendar.timeInMillis,
        endPendingIntent
    )
}

and use alarm manager with workmanager for intervals lower than 15 min for greater than 15 min use just workmanager and set your interval and start foreground service

0
VonC On

Your implementation of AlarmManager to schedule a foreground service is experiencing issues when the app is not actively running in the background. That is a common challenge in Android development due to various system restrictions, especially on newer Android versions.

You have:

+---------------------+      +---------------------------+      +---------------------------+
| SettingActivity     |      | TimeBasedScheduler        |      | SocketManagerService      |
| - setupForeground-  |      | - onReceive()             |      | - Foregrnd Service Logic  |
|   ServiceAlarms()   |----->|   * START_SERVICE Action  |----->|                           |
|                     |      |   * STOP_SERVICE Action   |      |                           |
+---------------------+      +---------------------------+      +---------------------------+
        |                                  |                            |
        | Sets Alarms                      | Starts/Stops Foreground    |
        | for START/STOP                   | Service based on           |
        |                                  | received action            |
        +----------------------------------+----------------------------+

I read in Android / Background Work Overview

Alarms are a special use case that are not a part of background work. You should execute background work through the two solutions outlined above, coroutines and WorkManager.

You should only use AlarmManager only for scheduling exact alarms such as alarm clocks or calendar events. When using AlarmManager to schedule background work, it wakes the device from Doze mode and its use can therefore have a negative impact on battery life and overall system health. Your app is responsible for such impacts.

See also "WorkManager vs AlarmManager, what to use depending on the case?".

Since WorkManager is designed to be compatible with Doze mode and App Standby limitations, you could use OneTimeWorkRequest with setInitialDelay for scheduling.

public class TimeBasedScheduler extends Worker {

    public TimeBasedScheduler(@NonNull Context context, @NonNull WorkerParameters workerParams) {
        super(context, workerParams);
    }

    @NonNull
    @Override
    public Result doWork() {
        // Your service logic here
        return Result.success();
    }
}

And to schedule this in your SettingActivity:

private void scheduleWork() {
    // Define constraints like network type, charging status, etc.
    Constraints constraints = new Constraints.Builder()
            .build();

    // Schedule your work
    OneTimeWorkRequest workRequest = new OneTimeWorkRequest.Builder(TimeBasedScheduler.class)
            .setInitialDelay(1, TimeUnit.HOURS) // Set the delay as per your requirement
            .setConstraints(constraints)
            .build();

    WorkManager.getInstance(this).enqueue(workRequest);
}

Note: for API level 21 and above, JobScheduler is an effective way to schedule jobs. Jobs can be set to persist across reboots and respect device idle states.