package org.terst.feedreader.work import android.app.AlarmManager import android.app.PendingIntent import android.content.BroadcastReceiver import android.content.Context import android.content.Intent import android.os.Build import android.util.Log import java.util.Calendar private const val TAG = "MorningFetch" /** * Wakes the device at a fixed time every morning to run a feed fetch, so the app has * fresh articles ready before the user ever opens it. Uses an exact alarm * (setExactAndAllowWhileIdle) specifically so this one wake survives Doze without * requesting the much broader "ignore battery optimizations" exemption -- the user wants * battery optimization left on otherwise, just this one daily wake-up. */ class MorningFetchReceiver : BroadcastReceiver() { override fun onReceive(context: Context, intent: Intent) { try { // A reboot only needs the alarm re-armed (AlarmManager alarms don't survive a // restart) -- an actual fetch isn't due just because the phone restarted. if (intent.action != Intent.ACTION_BOOT_COMPLETED) { FeedFetchWorker.fetchNow(context) } } catch (e: Exception) { Log.e(TAG, "Failed to enqueue morning fetch for action=${intent.action}", e) } scheduleNext(context) } companion object { private const val HOUR = 5 private const val MINUTE = 0 private const val ACTION_MORNING_FETCH = "org.terst.feedreader.MORNING_FETCH" private fun nextTriggerMillis(): Long { val now = Calendar.getInstance() val next = Calendar.getInstance().apply { set(Calendar.HOUR_OF_DAY, HOUR) set(Calendar.MINUTE, MINUTE) set(Calendar.SECOND, 0) set(Calendar.MILLISECOND, 0) } if (next.timeInMillis <= now.timeInMillis) { next.add(Calendar.DAY_OF_MONTH, 1) } return next.timeInMillis } /** No-ops quietly if the exact-alarm permission isn't granted yet (Android 12+) -- * MainActivity is what prompts for that; this just skips scheduling until it's there. * Also survives any failure from AlarmManager itself (logged, not crashed): this runs * unconditionally from MainActivity.onCreate/onResume on every single launch, so an * unguarded exception here would crash the app on every open -- a background wake-up * alarm failing to schedule isn't worth that; just log it and let the next app open * try again. */ fun scheduleNext(context: Context) { try { val alarmManager = context.getSystemService(Context.ALARM_SERVICE) as? AlarmManager ?: return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S && !alarmManager.canScheduleExactAlarms()) { return } val intent = Intent(context, MorningFetchReceiver::class.java).setAction(ACTION_MORNING_FETCH) val pendingIntent = PendingIntent.getBroadcast( context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE ) alarmManager.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, nextTriggerMillis(), pendingIntent) } catch (e: Exception) { Log.e(TAG, "Failed to schedule morning fetch alarm", e) } } } }