Production cleanup: enable R8, add ProGuard rules, validate API input
- Enable R8 minification and resource shrinking for release builds - Add ProGuard keep rules for Ktor, kotlinx.serialization, Room - Validate hour/minute range in POST /set endpoint - Guard wake lock release on server start failure - Remove unused template colors from colors.xml - Rewrite README with curl examples, security note, troubleshooting Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
5ad7c6cee8
commit
6032e9fd07
5 changed files with 103 additions and 55 deletions
91
README.md
91
README.md
|
|
@ -1,64 +1,80 @@
|
|||
# Helios Alarm Clock
|
||||
|
||||
An Android alarm clock app with a built-in HTTP server, designed to be controlled remotely from a Raspberry Pi or any device on the local network.
|
||||
An Android alarm clock with an embedded HTTP server for remote control from a Raspberry Pi or any device on the local network.
|
||||
|
||||
## Features
|
||||
|
||||
- **HTTP API** — Embedded Ktor server (port 8080) for remote alarm management
|
||||
- **In-app UI** — Set and remove alarms with a Material 3 time picker
|
||||
- **Reliable alarms** — Uses `AlarmManager.setExactAndAllowWhileIdle()` to fire through Doze mode
|
||||
- **Full-screen alarm** — Wakes the screen, plays the system alarm sound, and vibrates
|
||||
- **DND bypass** — Alarm audio uses `USAGE_ALARM` to ring even in Do Not Disturb mode
|
||||
- **Persistent server** — Foreground service with wake lock keeps the HTTP server alive
|
||||
- **Boot survival** — Server and alarms reschedule automatically after reboot
|
||||
- **Auto-cleanup** — Fired alarms are automatically deleted from the database
|
||||
- **HTTP API** on port 8080 for remote alarm management
|
||||
- **Material 3 UI** with time picker and alarm list
|
||||
- **Reliable alarms** via `AlarmManager.setExactAndAllowWhileIdle()` (survives Doze)
|
||||
- **Full-screen alarm** with system alarm sound and DND bypass
|
||||
- **Persistent server** as a foreground service with wake lock
|
||||
- **Boot survival** — server and alarms reschedule after reboot
|
||||
- **Auto-cleanup** — fired alarms are deleted automatically
|
||||
|
||||
## HTTP API
|
||||
|
||||
All endpoints are served on port `8080`.
|
||||
All endpoints listen on port `8080`. Replace `PHONE_IP` with the IP shown in the app.
|
||||
|
||||
### Set an alarm
|
||||
|
||||
```
|
||||
POST /set
|
||||
Content-Type: application/json
|
||||
|
||||
{"hour": 7, "minute": 30, "label": "Wake up"}
|
||||
```bash
|
||||
curl -X POST http://PHONE_IP:8080/set \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"hour": 7, "minute": 30, "label": "Wake up"}'
|
||||
```
|
||||
|
||||
Returns `201 Created` with `{"id": "<uuid>"}`.
|
||||
Response: `201 Created`
|
||||
```json
|
||||
{"id": "550e8400-e29b-41d4-a716-446655440000"}
|
||||
```
|
||||
|
||||
`hour` must be 0-23, `minute` must be 0-59. `label` is optional (defaults to empty).
|
||||
|
||||
### Remove an alarm
|
||||
|
||||
```
|
||||
POST /rm
|
||||
Content-Type: application/json
|
||||
|
||||
{"id": "<uuid>"}
|
||||
```bash
|
||||
curl -X POST http://PHONE_IP:8080/rm \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"id": "550e8400-e29b-41d4-a716-446655440000"}'
|
||||
```
|
||||
|
||||
### List alarms
|
||||
|
||||
```
|
||||
GET /list
|
||||
```bash
|
||||
curl http://PHONE_IP:8080/list
|
||||
```
|
||||
|
||||
Returns a JSON array of all scheduled alarms.
|
||||
|
||||
## Security
|
||||
|
||||
The HTTP server has **no authentication**. Anyone on the same network can set or remove alarms. Only run this on a trusted local network.
|
||||
|
||||
## Tech Stack
|
||||
|
||||
- Kotlin, Jetpack Compose, Material 3
|
||||
- MVVM architecture with Hilt dependency injection
|
||||
- Room database for alarm persistence
|
||||
- MVVM with Hilt dependency injection
|
||||
- Room database for persistence
|
||||
- Ktor CIO embedded HTTP server
|
||||
- AlarmManager with exact alarms
|
||||
- Foreground service (connectedDevice type) for the HTTP server
|
||||
- Foreground service (`connectedDevice` type)
|
||||
|
||||
## Requirements
|
||||
|
||||
- Android 8.0+ (API 26)
|
||||
- Target SDK 36 (Android 16)
|
||||
- Permissions: exact alarms, notifications, foreground service, wake lock, internet
|
||||
|
||||
## Permissions
|
||||
|
||||
| Permission | Purpose |
|
||||
|---|---|
|
||||
| `INTERNET` / `ACCESS_NETWORK_STATE` | HTTP server |
|
||||
| `FOREGROUND_SERVICE_CONNECTED_DEVICE` | Keep server alive |
|
||||
| `SCHEDULE_EXACT_ALARM` / `USE_EXACT_ALARM` | Precise alarm timing |
|
||||
| `USE_FULL_SCREEN_INTENT` | Wake screen on alarm |
|
||||
| `WAKE_LOCK` | Prevent CPU sleep |
|
||||
| `RECEIVE_BOOT_COMPLETED` | Restart after reboot |
|
||||
| `POST_NOTIFICATIONS` | Service + alarm notifications |
|
||||
|
||||
## Building
|
||||
|
||||
|
|
@ -66,4 +82,21 @@ Returns a JSON array of all scheduled alarms.
|
|||
./gradlew assembleRelease
|
||||
```
|
||||
|
||||
The APK will be at `app/build/outputs/apk/release/app-release.apk`.
|
||||
The APK is at `app/build/outputs/apk/release/app-release.apk`.
|
||||
|
||||
## Installing via ADB
|
||||
|
||||
```bash
|
||||
adb install app/build/outputs/apk/release/app-release.apk
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
- **Server not reachable**: Ensure the phone and client are on the same Wi-Fi network. Check that battery optimization is disabled for the app.
|
||||
- **Alarm doesn't fire**: Grant exact alarm permission in system settings. Disable battery optimization for the app.
|
||||
- **Notification not showing**: Grant notification permission (Android 13+).
|
||||
- **Server dies in background**: Some OEMs aggressively kill background services. Disable battery optimization and lock the app in recents.
|
||||
|
||||
## License
|
||||
|
||||
MIT
|
||||
|
|
|
|||
|
|
@ -34,7 +34,8 @@ android {
|
|||
}
|
||||
buildTypes {
|
||||
release {
|
||||
isMinifyEnabled = false
|
||||
isMinifyEnabled = true
|
||||
isShrinkResources = true
|
||||
proguardFiles(
|
||||
getDefaultProguardFile("proguard-android-optimize.txt"),
|
||||
"proguard-rules.pro"
|
||||
|
|
|
|||
42
app/proguard-rules.pro
vendored
42
app/proguard-rules.pro
vendored
|
|
@ -1,21 +1,27 @@
|
|||
# Add project specific ProGuard rules here.
|
||||
# You can control the set of applied configuration files using the
|
||||
# proguardFiles setting in build.gradle.
|
||||
#
|
||||
# For more details, see
|
||||
# http://developer.android.com/guide/developing/tools/proguard.html
|
||||
# Ktor — keep CIO engine and routing
|
||||
-keep class io.ktor.** { *; }
|
||||
-keepclassmembers class io.ktor.** { *; }
|
||||
-dontwarn io.ktor.**
|
||||
|
||||
# If your project uses WebView with JS, uncomment the following
|
||||
# and specify the fully qualified class name to the JavaScript interface
|
||||
# class:
|
||||
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
||||
# public *;
|
||||
#}
|
||||
# kotlinx.serialization — keep @Serializable classes
|
||||
-keepattributes *Annotation*, InnerClasses
|
||||
-dontnote kotlinx.serialization.AnnotationsKt
|
||||
-keepclassmembers @kotlinx.serialization.Serializable class ** {
|
||||
*** Companion;
|
||||
}
|
||||
-keepclasseswithmembers class ** {
|
||||
kotlinx.serialization.KSerializer serializer(...);
|
||||
}
|
||||
-keep,includedescriptorclasses class com.example.helios_alarm_clock.**$$serializer { *; }
|
||||
-keepclassmembers class com.example.helios_alarm_clock.** {
|
||||
*** Companion;
|
||||
}
|
||||
-keepclasseswithmembers class com.example.helios_alarm_clock.** {
|
||||
kotlinx.serialization.KSerializer serializer(...);
|
||||
}
|
||||
|
||||
# Uncomment this to preserve the line number information for
|
||||
# debugging stack traces.
|
||||
#-keepattributes SourceFile,LineNumberTable
|
||||
# Room — keep entities
|
||||
-keep class com.example.helios_alarm_clock.data.AlarmEntity { *; }
|
||||
|
||||
# If you keep the line number information, uncomment this to
|
||||
# hide the original source file name.
|
||||
#-renamesourcefileattribute SourceFile
|
||||
# SLF4J (Ktor dependency) — suppress missing impl warnings
|
||||
-dontwarn org.slf4j.**
|
||||
|
|
|
|||
|
|
@ -54,7 +54,13 @@ class KtorService : Service() {
|
|||
super.onCreate()
|
||||
acquireWakeLock()
|
||||
startForeground(NOTIFICATION_ID, buildNotification())
|
||||
try {
|
||||
startServer()
|
||||
} catch (e: Exception) {
|
||||
releaseWakeLock()
|
||||
stopSelf()
|
||||
return
|
||||
}
|
||||
rescheduleAlarms()
|
||||
}
|
||||
|
||||
|
|
@ -111,6 +117,13 @@ class KtorService : Service() {
|
|||
post("/set") {
|
||||
try {
|
||||
val req = call.receive<SetAlarmRequest>()
|
||||
if (req.hour !in 0..23 || req.minute !in 0..59) {
|
||||
call.respond(
|
||||
HttpStatusCode.BadRequest,
|
||||
ErrorResponse("hour must be 0-23, minute must be 0-59")
|
||||
)
|
||||
return@post
|
||||
}
|
||||
val id = UUID.randomUUID().toString()
|
||||
|
||||
val now = Calendar.getInstance()
|
||||
|
|
|
|||
|
|
@ -1,10 +1,5 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<color name="purple_200">#FFBB86FC</color>
|
||||
<color name="purple_500">#FF6200EE</color>
|
||||
<color name="purple_700">#FF3700B3</color>
|
||||
<color name="teal_200">#FF03DAC5</color>
|
||||
<color name="teal_700">#FF018786</color>
|
||||
<color name="black">#FF000000</color>
|
||||
<color name="white">#FFFFFFFF</color>
|
||||
</resources>
|
||||
Loading…
Add table
Add a link
Reference in a new issue