1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
|
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)
}
}
}
}
|