6.5 KiB
6.5 KiB
Architecture: Uhr (Android, ohne Google)
Basis: recon.md, features.csv. Same conventions as wetter/: one native app, no server, no accounts.
No network permission at all. World clock cities come from the system time zone database, sounds from the device and the app.
Stack
| Schicht | Wahl | warum |
|---|---|---|
| Sprache/UI | Kotlin + Jetpack Compose, Material 3 Expressive | same as Wetter, shared look and know-how |
| Persistenz | Room (alarms, instances, timers, cities) + DataStore (settings, stopwatch, bedtime) | relational where it matters, key-value for singletons |
| Alarme | AlarmManager.setAlarmClock() |
exempt from Doze, shows the alarm icon in the status bar, the only API that is reliable for wake-up alarms |
| Klingeln | foreground service (type systemExempted/mediaPlayback) + full-screen-intent notification → ringing activity over the lock screen |
the pattern Android expects for alarm clocks |
| Timer/Stoppuhr | state in DB/DataStore, end_at / elapsedRealtime based; foreground service with live notification only while running |
survives process death; no ticking in the background |
| Widgets | Jetpack Glance + RemoteViews TextClock/AnalogClock |
clocks tick without app updates |
| Bildschirmschoner | DreamService |
system screensaver API |
| Ortsliste Weltuhr | java.time.ZoneId + localized names from ICU (android.icu.util.TimeZone display names, exemplar cities) |
offline, no city API needed |
| Töne | RingtoneManager (system alarm sounds) + own files via ACTION_OPEN_DOCUMENT (persisted URI permission) + bundled CC0 sounds |
no streaming services |
| Build/Verteilung | Gradle KTS, minSdk 29, targetSdk 36; release via scripts/release.sh uhr (Obtainium) |
same pipeline as Wetter |
Permissions
| permission | why |
|---|---|
USE_EXACT_ALARM (API 33+), SCHEDULE_EXACT_ALARM (≤32) |
alarm clock apps may use exact alarms without asking |
USE_FULL_SCREEN_INTENT |
ringing screen over the lock screen (Android 14+: check canUseFullScreenIntent, guide the user to the setting) |
POST_NOTIFICATIONS |
upcoming, snoozed, missed, timer, stopwatch |
FOREGROUND_SERVICE (+ type) |
ringing, running timers |
RECEIVE_BOOT_COMPLETED |
reschedule after reboot |
VIBRATE, WAKE_LOCK |
ringing |
No INTERNET, no location.
Schema
See schema.sql: 4 tables (alarms, alarm_instances, timers, cities), singletons in DataStore. Access rules: single user, app-private storage, no RLS needed.
Hard constraints: one bedtime wake alarm (partial unique index); a running timer always has end_at (check constraint); one instance row per alarm (primary key).
Interne API (Repositories / Use Cases)
| Aufruf | macht | Flow |
|---|---|---|
AlarmRepo.observe() |
alarms + next instance, sorted by time | F01 |
AlarmRepo.upsert(alarm) |
save, then Scheduler.reschedule(alarm) |
F01, F02 |
AlarmRepo.delete(id) / undo |
delete, cancel PendingIntent | F02 |
Scheduler.reschedule(alarm) |
compute next fire (days, skip_until, DST-safe via ZonedDateTime), write instance, setAlarmClock |
all |
Scheduler.rescheduleAll() |
on boot, TIME_SET, TIMEZONE_CHANGED, app update |
— |
Ringing.start(alarmId) |
foreground service: sound (gradual volume), vibration, full-screen activity, silence-after timeout | F03 |
Ringing.snooze() / dismiss() |
instance → snoozed (fire_at + snooze) / next occurrence | F03 |
AlarmRepo.dismissNext(id) |
skip the upcoming ring only | F04 |
TimerRepo.add/pause/resume/addMinute/reset/delete |
state transitions with end_at, schedule setExactAndAllowWhileIdle for the end |
F05 |
Stopwatch.start/stop/lap/reset |
DataStore singleton | F06 |
CityRepo.search(q) / add / reorder / delete |
offline zone search | F07 |
Bedtime.save(schedule) |
writes bedtime + the wake alarm (is_bedtime_wake) + reminder alarm |
F08 |
Sounds.list() / preview(uri) |
ringtones + bundled + picked files | F09 |
Background
| Auslöser | macht |
|---|---|
setAlarmClock fire → AlarmReceiver |
start ringing service |
| 2 h before an alarm (inexact alarm) | "upcoming alarm" notification with "Dismiss now" |
| silence-after timeout | stop ringing, instance → missed, missed notification |
BOOT_COMPLETED, TIME_SET, TIMEZONE_CHANGED, MY_PACKAGE_REPLACED |
rescheduleAll() |
| timer end (exact alarm) | timer ringing |
| bedtime reminder (exact) | reminder notification |
Was beißt
- Time zones and DST: alarms are wall-clock; the next fire is computed with
ZonedDateTimein the current zone, so 7:00 stays 7:00 after a zone change; a time skipped by DST rings at the first valid minute after. - Doze / OEM battery killers: only
setAlarmClockis reliable; show a one-time hint for battery optimization on aggressive OEMs. - Android 14+ full-screen intent: may be off for sideloaded apps → detect and explain, fall back to a heads-up notification that still rings.
- Race conditions: snooze and dismiss from the notification and the activity at the same time → all transitions go through one
Mutexin the ringing service. - Process death: timers and stopwatch keep only timestamps, never counters.
- Two alarms at the same time: ring both labels in one screen; the second does not cancel the first.
- Sounds: a picked file can disappear → fall back to the default sound, never ring silently by accident.
- No network: nothing to rate-limit, nothing to leak; app is F-Droid friendly.
Build-Reihenfolge
- Vertical slice (S01, S03, S11): add an alarm, it rings at the time with sound over the lock screen, stop. Tables
alarms,alarm_instances. Proves AlarmManager + full-screen intent. - Must alarms (S02, S04, S15): expanded card, days, label, vibrate, sound picker, snooze, delete/undo, snoozed notification, settings for snooze/silence-after, reboot/time-change handling.
- Timer + stopwatch (S08, S09, S12): keypad, multiple timers, ring progress, background notifications, laps. Table
timers. - World clock (S06, S07): local time, cities offline, reorder/delete, analog/digital. Table
cities. - Design pass: expressive cards, bottom bar, big time typography, slide-to-act.
- Widgets (S16): digital, analog, next alarm, style options like Wetter.
- Should: pause alarm, dismiss early, upcoming/missed notifications, gradual volume, volume buttons, bedtime (S10), own sound files, settings (S13).
- Could: sunrise alarm, sleep sounds, screensaver (S14), world clock widget, automatic home clock.