Lower minSdk to 29 (Android 10) — closes #2#4
Conversation
…p/build.gradle.kts\n- Manifest: add READ_EXTERNAL_STORAGE / WRITE_EXTERNAL_STORAGE (maxSdk 32); keep specialUse (tolerated on older platforms)\n- ScreenshotDetectionService: only pass FOREGROUND_SERVICE_TYPE_SPECIAL_USE on API 34+ (was >= Q, which crashed on 29-33)\n- New StoragePermissions helper: SDK-aware storage-permission check + request (fixes permanent 'Permissions Required' trap on 29-32)\n- ScreenshotCleanupWorker: gate silent background deletion to API 30+ (All-Files); manual/notification deletes still work on 29+\n- HomeScreen: use StoragePermissions helper; isAllFilesManager honest on < R\n- Docs/CHANGELOG: reflect Android 10+ support and the 29 auto-cleanup limitation
…minSdk to 29 exposed 10 NewApi lint errors (previously hidden at minSdk 34):\n- MainActivity: guard high-refresh-rate block (getDisplay/supportedModes/preferredDisplayModeId need API 30)\n- ScreenshotRepository: MediaStore.createDeleteRequest Collection overload is API 30; add API-29 fallback using direct delete + RecoverableSecurityException consent\n- avd_auto_delete.xml: system_accent1_* colors only exist on API 31+; move original to drawable-v31/ and add an API-29-safe hex default\n- themes.xml: windowSplashScreenBackground is compat-lib backed; suppress the lint false-positive with tools:targetApi=31\n\nVerified: ./gradlew lint test assembleDebug BUILD SUCCESSFUL (0 errors).
… system_accent1_100/_300 (tones 100/300 are near-white),\nso against the icon background it read as colorless and did not visibly follow\nthe wallpaper. Drive the icon from saturated dynamic Material You tones\n(system_accent1_200 fill, pulsing to system_accent1_500) and use the public,\nwallpaper-derived android:attr/colorBackground for the icon circle so the\nsplash follows Material You on Android 12+. The drawable-v31 version is used\non API 31+; the default hex drawable remains the <31 fallback.\n\nVerified: ./gradlew lint assembleRelease BUILD SUCCESSFUL (0 errors).
Automate pre-release test-APK flow (Closes #5)
…r\n\nThe reminder was a periodic WorkManager job whose initial delay clamped to 0\nwhen cleanup was <30 min away and whose inexact periodic scheduling let the\nheads-up be deferred/coalesced (worse after minSdk lowered to 29).\n\n- Add CleanupReminderReceiver (broadcast) that shows the heads-up when there\n are archived screenshots pending and re-arms the next day's alarm.\n- CleanupScheduler.setReminderAlarm uses AlarmManager.setExactAndAllowWhileIdle\n (RTC_WAKEUP) with a setAndAllowWhileIdle fallback on API 31+ without\n SCHEDULE_EXACT_ALARM; computeReminderTimeMillis clamps sub-lead-time cleanups\n to now instead of colliding with cleanup.\n- BootReceiver re-arms the reminder alarm after reboot.\n- Manifest: register CleanupReminderReceiver, add SCHEDULE_EXACT_ALARM.\n- Remove obsolete CleanupReminderWorker + unused WORK_REMINDER_NAME.\n- Add CleanupSchedulerReminderTest for the reminder timing math.\n\nVerified: ./gradlew lint test assembleDebug BUILD SUCCESSFUL.
…- Remove the push trigger so branch pushes no longer create a test-<branch>\n pre-release (only pull_request to main does, keyed to pr-<number>).\n- Simplify the tag step to always use pr-<number>.\n- Rename workflow file build-apk.yml -> build-pr-test-apk.yml and update\n its name/comments to reflect PR-only scope.
|
Review of PR #4 (Lower minSdk to 29). Overall the build/manifest/foreground-service changes are solid and CI is green. One blocking bug re-introduces the exact permission trap this PR targets, plus minor nits.\n\n## ⛔ Blocking: storage-permission result handling still hardcoded to READ_MEDIA_IMAGES (API 29-32)\n\n |
…rmManager\n\nThe reminder was a periodic WorkManager job whose initial delay clamped to 0\nwhen cleanup was <30 min away and whose inexact periodic scheduling let the\nheads-up be deferred/coalesced (worse after minSdk lowered to 29).\n\n- Add CleanupReminderReceiver (broadcast) that shows the heads-up when there\n are archived screenshots pending and re-arms the next day's alarm.\n- CleanupScheduler.setReminderAlarm uses AlarmManager.setExactAndAllowWhileIdle\n (RTC_WAKEUP) with a setAndAllowWhileIdle fallback on API 31+ without\n SCHEDULE_EXACT_ALARM; computeReminderTimeMillis clamps sub-lead-time cleanups\n to now instead of colliding with cleanup.\n- BootReceiver re-arms the reminder alarm after reboot.\n- Manifest: register CleanupReminderReceiver, add SCHEDULE_EXACT_ALARM.\n- Remove obsolete CleanupReminderWorker + unused WORK_REMINDER_NAME.\n- Add CleanupSchedulerReminderTest for the reminder timing math.\n\nVerified: ./gradlew lint test assembleDebug BUILD SUCCESSFUL." This reverts commit ae4f689.
…th)\n\nThe permission-launcher result callback read permissions[READ_MEDIA_IMAGES]\nto update hasStoragePermission, but on API 29-32 the app requests\nREAD_EXTERNAL_STORAGE (via StoragePermissions.requiredStoragePermission()),\nso READ_MEDIA_IMAGES is absent from the result map and the ?: fallback kept\nthe stale un-granted value — stranding those devices in 'Permissions Required'\neven after granting.\n\nRe-evaluate from StoragePermissions.hasStoragePermission(context) so the state\nis correct on every API level, consistent with the initial-state computation and\nthe single source of truth.\n\nVerified: ./gradlew compileDebugKotlin BUILD SUCCESSFUL; release APK installed\non OnePlus 8T (API 30).
Summary
Implements issue #2: lower
minSdkfrom 34 (Android 14) to 29 (Android 10) so ssJanitor supports older devices, including custom ROMs / hardware stuck on Android 10–12.Backward-compatible screenshot detection already used
MediaStore+ContentObserver(the API-29 columnsRELATIVE_PATH/IS_PENDING), so the real work was making the foreground service, permissions, and deletion model valid across the API 29–36 range.Changes
minSdk34 → 29 (app/build.gradle.kts).READ_EXTERNAL_STORAGE/WRITE_EXTERNAL_STORAGE(maxSdk=32); keepspecialUse+ subtype (tolerated on older platforms, only used at runtime on API 34+).ScreenshotDetectionServicenow passesFOREGROUND_SERVICE_TYPE_SPECIAL_USEonly on API 34+ (was>= Q, which crashed the FGS on 29–33).StoragePermissionshelper: SDK-aware storage check and request (usesREAD_EXTERNAL_STORAGEbelow 33,READ_MEDIA_IMAGESon 33+).ScreenshotCleanupWorker: silent background deletion gated to API 30+ (needs All-Files); manual/notification deletes viacreateDeleteRequeststill work on 29+.HomeScreen: uses theStoragePermissionshelper;isAllFilesManageris now honest below API 30.docs/features.md,docs/development.md, fastlane listing, and CHANGELOG updated to Android 10+ with the API-29 auto-cleanup caveat.Bugs fixed
READ_MEDIA_IMAGES(which doesn't exist below 33), so those devices could never grant storage access and detection never started.SPECIAL_USEtostartForegroundwhile the manifest didn't declare it threwSecurityExceptioninonCreate.isAllFilesManagerlie on API 29 — it was hardcodedtruebelow API 30 even though All-Files doesn't exist there, masking that silent auto-cleanup can't run.Verification
./gradlew assembleDebugandassembleReleaseboth BUILD SUCCESSFUL (including R8 minification andlintVital).minSdkVersion="29",READ/WRITE_EXTERNAL_STORAGE, and retainedspecialUse.docs/development.mdbefore merge.Closes #2