/** * SchedulingPolicy.java * * Policy configuration for notification scheduling, fetching, and retry behavior. * * This class is part of the Integration Point Refactor (PR1) SPI implementation. * It allows host apps to configure scheduling behavior including retry backoff, * prefetch timing, deduplication windows, and cache TTL. * * @author Matthew Raymer * @version 1.0.0 */ package com.timesafari.dailynotification; import androidx.annotation.NonNull; import androidx.annotation.Nullable; /** * Scheduling policy configuration * * Controls how the plugin schedules fetches, handles retries, manages * deduplication, and enforces TTL policies. * * This follows the TypeScript interface from src/types/content-fetcher.ts * and ensures consistency between JS and native configuration. */ public class SchedulingPolicy { /** * How early to prefetch before scheduled notification time (milliseconds) * * Example: If set to 300000 (5 minutes), and notification is scheduled for * 8:00 AM, the fetch will be triggered at 7:55 AM. * * Default: 5 minutes (300000ms) */ @Nullable public Long prefetchWindowMs; /** * Retry backoff configuration (required) * * Controls exponential backoff behavior for failed fetches */ @NonNull public RetryBackoff retryBackoff; /** * Maximum items to fetch per batch * * Limits the number of NotificationContent items that can be fetched * in a single operation. Helps prevent oversized responses. * * Default: 50 */ @Nullable public Integer maxBatchSize; /** * Deduplication window (milliseconds) * * Prevents duplicate notifications within this time window. Plugin * uses dedupeKey (or id) to detect duplicates. * * Default: 24 hours (86400000ms) */ @Nullable public Long dedupeHorizonMs; /** * Default cache TTL if item doesn't specify ttlSeconds (seconds) * * Used when NotificationContent doesn't have ttlSeconds set. * Determines how long cached content remains valid. * * Default: 6 hours (21600 seconds) */ @Nullable public Integer cacheTtlSeconds; /** * Whether exact alarms are allowed (Android 12+) * * Controls whether plugin should attempt to use exact alarms. * Requires SCHEDULE_EXACT_ALARM permission on Android 12+. * * Default: false (use inexact alarms) */ @Nullable public Boolean exactAlarmsAllowed; /** * Fetch timeout in milliseconds * * Maximum time to wait for native fetcher to complete. * Plugin enforces this timeout when calling fetchContent(). * * Default: 30 seconds (30000ms) */ @Nullable public Long fetchTimeoutMs; /** * Default constructor with required field * * @param retryBackoff Retry backoff configuration (required) */ public SchedulingPolicy(@NonNull RetryBackoff retryBackoff) { this.retryBackoff = retryBackoff; } /** * Retry backoff configuration * * Controls exponential backoff behavior for retryable failures. * Delay = min(max(minMs, lastDelay * factor), maxMs) * (1 + jitterPct/100 * random) */ public static class RetryBackoff { /** * Minimum delay between retries (milliseconds) * * First retry will wait at least this long. * Default: 2000ms (2 seconds) */ public long minMs; /** * Maximum delay between retries (milliseconds) * * Retry delay will never exceed this value. * Default: 600000ms (10 minutes) */ public long maxMs; /** * Exponential backoff multiplier * * Each retry multiplies previous delay by this factor. * Example: factor=2 means delays: 2s, 4s, 8s, 16s, ... * Default: 2.0 */ public double factor; /** * Jitter percentage (0-100) * * Adds randomness to prevent thundering herd. * Final delay = calculatedDelay * (1 + jitterPct/100 * random(0-1)) * Default: 20 (20% jitter) */ public int jitterPct; /** * Default constructor with sensible defaults */ public RetryBackoff() { this.minMs = 2000; this.maxMs = 600000; this.factor = 2.0; this.jitterPct = 20; } /** * Constructor with all parameters * * @param minMs Minimum delay (ms) * @param maxMs Maximum delay (ms) * @param factor Exponential multiplier * @param jitterPct Jitter percentage (0-100) */ public RetryBackoff(long minMs, long maxMs, double factor, int jitterPct) { this.minMs = minMs; this.maxMs = maxMs; this.factor = factor; this.jitterPct = jitterPct; } } /** * Create default policy with sensible defaults * * @return Default SchedulingPolicy instance */ @NonNull public static SchedulingPolicy createDefault() { SchedulingPolicy policy = new SchedulingPolicy(new RetryBackoff()); policy.prefetchWindowMs = 300000L; // 5 minutes policy.maxBatchSize = 50; policy.dedupeHorizonMs = 86400000L; // 24 hours policy.cacheTtlSeconds = 21600; // 6 hours policy.exactAlarmsAllowed = false; policy.fetchTimeoutMs = 30000L; // 30 seconds return policy; } }