fix(android): guard setExactAndAllowWhileIdle with canScheduleExactAlarms() check

SecurityException crash on Android 12+ when SCHEDULE_EXACT_ALARM special
access not user-granted. Now checks canScheduleExactAlarms() and falls back
to setAndAllowWhileIdle() (inexact, no special permission needed). Both
schedule() and cancel() wrapped in try/catch to prevent future alarm crashes.
This commit is contained in:
ssbg04 committed 2026-08-18 13:37:05 +08:00
1 parent 281a782b93
commit fa8b101b34
1 file changed
+31 -12
@@ -4,6 +4,7 @@ import android.app.AlarmManager
import android.app.PendingIntent
import android.content.Context
import android.content.Intent
import android.os.Build
import android.os.SystemClock
import android.util.Log
@@ -13,21 +14,39 @@ object AlarmScheduler {
private const val INTERVAL_MS = 2 * 60 * 1000L // 2 minutes
fun schedule(context: Context) {
val alarmManager = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager
val pi = buildPendingIntent(context)
alarmManager.cancel(pi)
alarmManager.setExactAndAllowWhileIdle(
AlarmManager.ELAPSED_REALTIME_WAKEUP,
SystemClock.elapsedRealtime() + INTERVAL_MS,
pi
)
Log.d(TAG, "scheduled: next check in ${INTERVAL_MS / 1000}s")
try {
val alarmManager = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager
val pi = buildPendingIntent(context)
alarmManager.cancel(pi)
val triggerAt = SystemClock.elapsedRealtime() + INTERVAL_MS
val canExact = Build.VERSION.SDK_INT < Build.VERSION_CODES.S ||
alarmManager.canScheduleExactAlarms()
if (canExact) {
alarmManager.setExactAndAllowWhileIdle(
AlarmManager.ELAPSED_REALTIME_WAKEUP, triggerAt, pi
)
Log.d(TAG, "scheduled (exact): next check in ${INTERVAL_MS / 1000}s")
} else {
alarmManager.setAndAllowWhileIdle(
AlarmManager.ELAPSED_REALTIME_WAKEUP, triggerAt, pi
)
Log.d(TAG, "scheduled (inexact): next check in ${INTERVAL_MS / 1000}s")
}
} catch (e: Exception) {
Log.e(TAG, "schedule failed: ${e.message}")
}
}
fun cancel(context: Context) {
val alarmManager = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager
alarmManager.cancel(buildPendingIntent(context))
Log.d(TAG, "cancelled")
try {
val alarmManager = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager
alarmManager.cancel(buildPendingIntent(context))
Log.d(TAG, "cancelled")
} catch (e: Exception) {
Log.e(TAG, "cancel failed: ${e.message}")
}
}
private fun buildPendingIntent(context: Context): PendingIntent {