Active hours schedule with screen-off and power-button wake override#58
Active hours schedule with screen-off and power-button wake override#58patbaumgartner wants to merge 2 commits into
Conversation
…erride Add an optional "Active Times" schedule that lets the frame turn its screen off and sleep the device during configured inactive hours, then wake itself back up at the next active period. Also add a manual power-button override so the user can briefly view the frame while it is asleep. Features - Per-weekday active schedule (multiple rules, multiple ranges per rule, overnight ranges supported) edited in a Compose-based editor. - During inactive hours the frame stops refreshing, dims to black, drops the keep-screen-on flags and (with device-admin permission) calls lockNow() to actually power the screen off and let the device sleep. - An AlarmManager RTC_WAKEUP alarm (setExactAndAllowWhileIdle, wakes from Doze) brings MainActivity to the foreground at the next active-start time and the screen powers back on. - Manual power-button override: pressing power while asleep shows the frame temporarily without FLAG_KEEP_SCREEN_ON, so the normal screen timeout still applies; when the screen turns off the schedule resumes and the wake alarm is re-armed. Implementation - Helpers: ActiveSchedule model + JSON (de)serialization, isActiveNow() and nextActiveStart() (empty schedule == always active so the screen never blacks out unconfigured). - MainActivity: tri-state isFrameInactive, setFrameActive(), screen on/off BroadcastReceiver for the override, wake alarm scheduling, lockDeviceIfPossible(), and onResume re-evaluation so an already-running instance woken via REORDER_TO_FRONT powers the screen on immediately. - ScheduleWakeReceiver: broadcast receiver that relaunches MainActivity. - FrameDeviceAdminReceiver + device_admin.xml (force-lock policy) for screen-off. - SettingsFragment: Active Times category (enable switch, schedule editor, screen-off permission toggle) with live summaries; Material dialogs. - ActiveScheduleActivity: Compose schedule editor. - Manifest: register the two receivers and the editor activity.
|
Warning Review limit reached
More reviews will be available in 48 minutes and 1 second. Learn how PR review limits work. Your organization has used up its prepaid credits, and credit purchases are no longer available. Enable the review add-on in the billing tab to keep reviews running — you're only billed for reviews past your plan's rate limits ($0.25/file). ⌛ How to resolve this issue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based credits. 🚦 How do rate limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please see our Fair Usage Limits Policy for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (2)
📝 WalkthroughWalkthroughAdds a configurable active-hours scheduling system to the Android frame app. New data models, parse/serialize/evaluate helpers, and a Compose schedule editor let users define per-weekday time rules. ChangesActive Schedule & Device Admin
Sequence Diagram(s)sequenceDiagram
participant User
participant SettingsFragment
participant ActiveScheduleActivity
participant Helpers
participant MainActivity
participant AlarmManager
participant DevicePolicyManager
User->>SettingsFragment: enable activeTimes switch
SettingsFragment->>MainActivity: loadSettings() triggers activeCheckRunnable
MainActivity->>Helpers: isActiveNow(schedule, now)
alt schedule says inactive
MainActivity->>DevicePolicyManager: lockNow()
MainActivity->>Helpers: nextActiveStart(schedule, now)
MainActivity->>AlarmManager: scheduleWakeAlarm(nextStart)
end
AlarmManager->>MainActivity: ACTION_SCHEDULE_WAKE fires via ScheduleWakeReceiver
MainActivity->>Helpers: isActiveNow(schedule, now)
MainActivity->>MainActivity: setFrameActive(true): wake screen, load settings
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 4
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@app/src/main/java/com/immichframe/immichframe/ActiveScheduleActivity.kt`:
- Around line 108-119: The save logic in ActiveScheduleActivity’s onClick
handler is silently filtering out invalid rules and allowing zero-length ranges,
which can turn a mistaken schedule into always-active behavior. Update the
validation before calling onSave(Helpers.ActiveSchedule(...)) to explicitly
reject rules with empty days, missing ranges, or any range where start equals
end instead of dropping them. Surface an error/validation message to the user
and only proceed when every rule in the rules list is complete and all
ActiveRange values are valid.
In `@app/src/main/java/com/immichframe/immichframe/MainActivity.kt`:
- Around line 877-887: The alarm resync logic in checkActiveTime is skipping
setFrameActive(false) when isFrameInactive is already true, which leaves
AlarmManager stuck on an old wake time after schedule changes. Update the
active-time handling so schedule changes always refresh the wake alarm even
while inactive/manual-override is set, and make sure a null next time clears any
previously scheduled alarm instead of keeping it. Apply the same fix to the
related logic around setFrameActive and any duplicate path near the other
referenced block.
- Around line 962-968: The on-screen-off flow in onScreenTurnedOff() always
clears manual override and calls setFrameActive(false), which can ignore the
current Active Times state or a schedule that has already become active. Update
this method to re-evaluate the schedule/state before ending isManualOverride,
and only deactivate the frame if the current conditions still require it. Use
the existing onScreenTurnedOff, isManualOverride, and setFrameActive logic to
keep the frame aligned with the latest schedule.
In `@app/src/main/java/com/immichframe/immichframe/SettingsFragment.kt`:
- Around line 67-72: Turning off Active Times currently only hides the nested
preferences in the chkActiveTimes change listener, so the existing wake
alarm/inactive frame state remains in effect. Update the Active Times off
transition in SettingsFragment’s preference change handling to explicitly clear
schedule side effects: reactivate the frame, cancel any pending schedule
enforcement/wake alarm, or trigger the corresponding cleanup path in
MainActivity. Make sure the new behavior is tied to the chkActiveTimes toggle
and is consistent with what MainActivity.onResume expects when activeTimes
becomes false.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: c4f3f1ee-f7ed-4bee-b59d-25b20e3041c1
📒 Files selected for processing (12)
app/src/main/AndroidManifest.xmlapp/src/main/java/com/immichframe/immichframe/ActiveScheduleActivity.ktapp/src/main/java/com/immichframe/immichframe/FrameDeviceAdminReceiver.ktapp/src/main/java/com/immichframe/immichframe/Helpers.ktapp/src/main/java/com/immichframe/immichframe/MainActivity.ktapp/src/main/java/com/immichframe/immichframe/ScheduleWakeReceiver.ktapp/src/main/java/com/immichframe/immichframe/SettingsFragment.ktapp/src/main/res/layout/pref_close_button.xmlapp/src/main/res/values/strings.xmlapp/src/main/res/values/styles.xmlapp/src/main/res/xml/device_admin.xmlapp/src/main/res/xml/settings_view.xml
- ActiveScheduleActivity: validate rules before saving; reject incomplete rules (no days / no ranges), malformed times, and zero-length ranges via a Toast instead of silently dropping them. Empty schedule still means always-active. - MainActivity.checkActiveTime: re-arm the wake alarm when already inactive so a changed schedule no longer leaves a stale wake time. - MainActivity.scheduleWakeAlarm: cancel any prior alarm before computing the next start so an empty/expired schedule clears the pending wake. - MainActivity.onScreenTurnedOff: re-evaluate the schedule (and the activeTimes toggle) before ending the manual override instead of always sleeping. - MainActivity.onResume: when Active Times is disabled while the frame is asleep, cancel the wake alarm and reactivate the frame so disabling the feature fully clears its side effects.
|
I’m pleasantly surprised by how well the new feature works on the Denver Frameo PFF-1042DWMK3. It wakes up at the scheduled times and goes back to sleep as expected. |
|
Sorry, just saw this. Let me have a look, and thanks for contributing! Some quick comments:
|
|
Thanks for taking a look! Device Admin: Fair point, Google has been strict about Power button behavior: It actually already works the way you describe, so we agree :) Pressing the power button while the frame is asleep is not just a quick peek. It sets a manual override that suppresses the schedule, so the frame wakes up and stays on until the screen turns off again or the next sleep transition kicks in. The motivation was exactly your scenario, plus one more: being able to open the app settings (or develop against the device) while it is inside its sleep window, without the schedule immediately forcing it back to sleep. I will clarify the PR description and code comments so "peek" does not suggest a temporary flash. |
|
Perfect! Yeah i think the word peek through me off. I haven't reviewed code yet either which would have likely cleared up the intent. |
|
I actually also fixed the coloring on the settings page and improved the close button 🙂 Take your time for the review. It's running on my device 😉 |
|
This looks REALLY great. I feel like Active Hours should replace the Screen Dimming, since this might confuse people and they both serve the same purpose. What do you think? |
|
I agree. But wanted to keep it backwards compatible, and not breaking any installations. Do you want me to remove the old dimming feature? |
|
Ya, I think so. They do the same thing, and your pickers make it real easy to recreate whatever they were using. Plus the additional true screen off function makes up for any inconvenience :). |
Summary
This PR adds an optional Active Times schedule that lets the photo frame turn its screen off and put the device to sleep during configured inactive hours, then wake itself back up at the next active period. It also adds a manual power-button override so you can briefly glance at the frame while it is asleep without disturbing the schedule.
Everything is opt-in: with the feature disabled (default) the frame behaves exactly as before.
Motivation
A wall-mounted frame usually only needs to be on during certain hours (e.g. daytime). Previously the screen stayed on 24/7 (or only dimmed). This lets the frame fully power its panel off overnight to save power / panel wear and reliably turn itself back on in the morning — while still letting you wake it on demand with the power button.
Features
1. Per-weekday active schedule
ActiveScheduleActivity) lets you define rules, each with a set of weekdays and one or more time ranges (HH:mm–HH:mm).22:00–06:00); the part before midnight is anchored to the day the range starts on.activeSchedule.2. Screen-off during inactive hours
When the current time falls outside every active range, the frame:
FLAG_KEEP_SCREEN_ON/FLAG_SHOW_WHEN_LOCKED,DevicePolicyManager.lockNow()to actually power the panel off and let the device sleep.Without the device-admin permission the frame still dims to black; with it, the panel truly turns off.
3. Self-wake at the next active time
AlarmManagerRTC_WAKEUPalarm at the next active-start time usingsetExactAndAllowWhileIdle, which fires even from Doze (available since API 23 =minSdk).ScheduleWakeReceiverreceives the alarm and bringsMainActivityto the foreground; the activity re-evaluates the schedule and powers the screen on.set()wake.4. Manual power-button override
This is the key on-demand feature:
BroadcastReceiverlistens forACTION_SCREEN_ONand shows the frame temporarily.FLAG_KEEP_SCREEN_ON, so the device's normal screen timeout still applies — the user gets a quick look, and the screen powers off on its own.ACTION_SCREEN_OFF), the override ends, the schedule resumes, and the wake alarm is re-armed for the next rule.Implementation notes
Helpers.kt—ActiveSchedule/ActiveRule/ActiveRangemodel, JSON (de)serialization, and the schedule math:isActiveNow()(handles same-day, overnight, andstart == endall-day ranges) andnextActiveStart()(scans minute-by-minute up to 8 days ahead).MainActivity.kt— tri-stateisFrameInactive(null/true/false) to avoid redundant transitions;setFrameActive()to enter/leave the inactive state; the screen on/off receiver for the override;wakeScreen()(briefSCREEN_BRIGHT_WAKE_LOCK+ACQUIRE_CAUSES_WAKEUP); wake-alarm scheduling/cancelling; andlockDeviceIfPossible().onResume()re-evaluates the schedule when active times are enabled. This is important: when the wake alarm brings an already-running instance forward viaREORDER_TO_FRONT,onCreate()does not run, so re-checking inonResume()powers the screen on immediately instead of waiting for the next periodic (30 s) check.ScheduleWakeReceiver.kt— broadcast receiver (not exported) that relaunchesMainActivity.FrameDeviceAdminReceiver.kt+res/xml/device_admin.xml— a minimal device-admin component using only theforce-lockpolicy, used solely to turn the screen off.SettingsFragment.kt— new Active Times preference category: an enable switch, a link to the schedule editor, and a screen-off-permission toggle that requests/clears device admin. Live summaries show the configured schedule and the permission state. Dialogs migrated toMaterialAlertDialogBuilder.ActiveScheduleActivity.kt— the Jetpack Compose schedule editor (add/remove rules, pick weekdays with quick presets, add/edit/remove time ranges via the time picker).res/xml/settings_view.xml,res/values/strings.xml,res/values/styles.xml,res/layout/pref_close_button.xml— settings UI, strings, theming (purple accent on controls), and a styled close button.AndroidManifest.xml— registersActiveScheduleActivity, theFrameDeviceAdminReceiver(withBIND_DEVICE_ADMIN), andScheduleWakeReceiver(with the custom wake action).Permissions
force-lock) — optional, user-granted from Settings. Only used to calllockNow()to power the screen off. If not granted, the frame falls back to dimming to black.Compatibility
minSdk 23is respected:setExactAndAllowWhileIdleis API 23;setShowWhenLocked/setTurnScreenOn/requestDismissKeyguardare guarded behindO_MR1with the legacy window flags as the baseline.Testing
Validated on a physical device (Android 6.0.1) with a structured 5-cycle harness exercising the full contract per cycle:
KEEP_SCREEN_ONabsent,SHOW_WHEN_LOCKEDset, alarm not cancelledKEEP_SCREEN_ONset, stays onResult: 85 / 85 assertions passed across 5 cycles. Window flags were verified via
dumpsys window windows, display/wakefulness viadumpsys power, and alarm arming viadumpsys alarm.Notes for reviewers
Summary by CodeRabbit
New Features
Bug Fixes