import type { AlarmPermissions } from '../services/alarmNative'; /** * What stands between this device and **delivering a real alarm**. * * Product ruling: a notification is never a delivery path. An alert must ring * and take over the screen; the only notification the app may post about a * firing alarm is the volume warning ("your alarm volume is not at maximum"). * So the grants below are not "nice to have, otherwise you get a notification" * — they are **blocking**: while one is missing, this device cannot deliver a * real alarm and must not present itself as armed. * * - **Exact alarms**: the alarm is scheduled with * `AlarmManager.setAlarmClock` (see `AlarmScheduler.kt`). Without the grant * there is no alarm clock, and — by the platform's own definition — no alarm * app classification, which is what the takeover permission depends on. * - **Full-screen intent**: restricted since Android 14 (API 34) and revoked by * default for ordinary apps. Without it the takeover cannot cover the lock * screen, so the device is not usable for alerts. * - **Notifications**: Android requires a notification for the long-running * foreground service that holds the armed socket, so without the grant the * device cannot keep listening while the app is closed. This is a platform * requirement, not a delivery mechanism. * * The battery exemption stays advisory: it makes background survival more * likely but does not gate delivery. */ export type ReadinessKey = 'exactAlarm' | 'fullScreenIntent' | 'notifications' | 'battery'; export interface ReadinessBlocker { key: ReadinessKey; title: string; body: string; actionLabel: string; /** Blocking: this device cannot deliver a real alarm, so the UI must say so. */ blocking: boolean; } /** Fixed copy — the banner and the settings checklist share it. */ export const READINESS_COPY: Record> = { exactAlarm: { title: 'Exact alarms', body: 'Without this, this device cannot fire a real alarm at the moment it is triggered. Allow exact alarms so the alarm clock can run.', actionLabel: 'Open settings', blocking: true, }, fullScreenIntent: { title: 'Full-screen takeover', body: 'Without this, this device cannot take over the screen when the alert fires. An alert has to be a real alarm, so this device is not usable for alerts until it is allowed.', actionLabel: 'Open settings', blocking: true, }, notifications: { title: 'Notifications', body: 'Without this, this device cannot keep listening while the app is closed — Android requires a notification for the background connection.', actionLabel: 'Allow', blocking: true, }, battery: { title: 'Background reliability', body: 'Without this, Android may freeze the app in the background and the armed socket stops listening.', actionLabel: 'Allow', blocking: false, }, }; export interface TakeoverReadiness { /** False while permissions are still unread — render nothing rather than guess. */ known: boolean; /** True when this device cannot deliver a real alarm. */ blocked: boolean; blockers: ReadinessBlocker[]; } /** * @param perms the Android permission snapshot, or `null` while it is loading */ export function takeoverReadiness(perms: AlarmPermissions | null): TakeoverReadiness { if (!perms) return { known: false, blocked: false, blockers: [] }; const blockers: ReadinessBlocker[] = []; if (!perms.exactAlarm) { blockers.push({ key: 'exactAlarm', ...READINESS_COPY.exactAlarm }); } if (!perms.fullScreenIntent) { blockers.push({ key: 'fullScreenIntent', ...READINESS_COPY.fullScreenIntent }); } if (!perms.notifications) { blockers.push({ key: 'notifications', ...READINESS_COPY.notifications }); } if (!perms.batteryUnrestricted) { blockers.push({ key: 'battery', ...READINESS_COPY.battery }); } return { known: true, blocked: blockers.some(b => b.blocking), blockers, }; } /** True only when every blocking grant is present. */ export function canDeliverRealAlarm(perms: AlarmPermissions | null): boolean { const readiness = takeoverReadiness(perms); return readiness.known && !readiness.blocked; } /** * Honest one-liner for the "this device" card. Never claims readiness while a * blocking grant is missing. */ export function deliverySummary(perms: AlarmPermissions | null): string { const readiness = takeoverReadiness(perms); if (!readiness.known) return 'Checking how this device can alert you…'; if (!perms?.exactAlarm) { return 'This device cannot fire a real alarm — exact alarms are not allowed.'; } if (!perms.fullScreenIntent) { return 'This device cannot take over the screen — the full-screen takeover is not allowed.'; } if (!perms.notifications) { return 'This device cannot keep listening while the app is closed — notifications are not allowed.'; } return 'This device fires a real alarm at maximum volume when the owner triggers the alert.'; }